keycloak-aplcache

Details

diff --git a/docbook/reference/en/en-US/master.xml b/docbook/reference/en/en-US/master.xml
index 37c0796..d87cb04 100755
--- a/docbook/reference/en/en-US/master.xml
+++ b/docbook/reference/en/en-US/master.xml
@@ -10,6 +10,7 @@
                 <!ENTITY EAP6Adapter SYSTEM "modules/eap6-adapter.xml">
                 <!ENTITY SocialConfig SYSTEM "modules/social-config.xml">
                 <!ENTITY SocialFacebook SYSTEM "modules/social-facebook.xml">
+                <!ENTITY SocialGitHub SYSTEM "modules/social-github.xml">
                 <!ENTITY SocialGoogle SYSTEM "modules/social-google.xml">
                 <!ENTITY SocialTwitter SYSTEM "modules/social-twitter.xml">
                 <!ENTITY SocialProviderSPI SYSTEM "modules/social-spi.xml">
@@ -73,6 +74,7 @@
         </para>
         &SocialConfig;
         &SocialFacebook;
+        &SocialGitHub;
         &SocialGoogle;
         &SocialTwitter;
         &SocialProviderSPI;
diff --git a/docbook/reference/en/en-US/modules/social-github.xml b/docbook/reference/en/en-US/modules/social-github.xml
new file mode 100644
index 0000000..4315f6d
--- /dev/null
+++ b/docbook/reference/en/en-US/modules/social-github.xml
@@ -0,0 +1,27 @@
+<section id="social-github">
+    <title>GitHub</title>
+    <para>
+        To enable login with Google you first have to create an application in
+        <ulink url="https://github.com/settings/applications">GitHub Settings</ulink>. Then you need to copy
+        the client id and secret into the Keycloak Admin Console.
+    </para>
+    <orderedlist>
+        <listitem>
+            <para>
+                Log in to <ulink url="https://github.com/settings/applications">GitHub Settings</ulink>. Click the
+                <literal>Register new application</literal> button. Use any value for <literal>Application name</literal>,
+                <literal>Homepage URL</literal> and <literal>Application Description</literal> you want. In <literal>Authorization callback URL</literal>
+                enter the <link linkend="social-callbackurl">social callback url</link> for your realm. Click the
+                <literal>Register application</literal> button.
+            </para>
+        </listitem>
+        <listitem>
+            <para>
+                Copy <literal>Client ID</literal> and <literal>Client secret</literal> from the
+                <ulink url="https://github.com/settings/applications">GitHub Settings</ulink> into the settings
+                page in the Keycloak Admin Console as the <literal>Key</literal> and <literal>Secret</literal>. Then click
+                <literal>Save</literal> in the Keycloak Admin Console to enable login with Google.
+            </para>
+        </listitem>
+    </orderedlist>
+</section>
\ No newline at end of file
diff --git a/services/src/main/java/org/keycloak/services/resources/SocialResource.java b/services/src/main/java/org/keycloak/services/resources/SocialResource.java
index a4efab2..4914c8a 100755
--- a/services/src/main/java/org/keycloak/services/resources/SocialResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/SocialResource.java
@@ -106,10 +106,10 @@ public class SocialResource {
         RequestDetails requestData = getRequestDetails(queryParams);
         SocialProvider provider = SocialLoader.load(requestData.getProviderId());
 
-        String realmId = requestData.getClientAttribute("realmId");
+        String realmName = requestData.getClientAttribute("realm");
 
         RealmManager realmManager = new RealmManager(session);
-        RealmModel realm = realmManager.getRealm(realmId);
+        RealmModel realm = realmManager.getRealmByName(realmName);
 
         OAuthFlows oauth = Flows.oauth(realm, request, uriInfo, authManager, tokenManager);
 
@@ -186,12 +186,12 @@ public class SocialResource {
 
     @GET
     @Path("{realm}/login")
-    public Response redirectToProviderAuth(@PathParam("realm") final String realmId,
+    public Response redirectToProviderAuth(@PathParam("realm") final String realmName,
                                            @QueryParam("provider_id") final String providerId, @QueryParam("client_id") final String clientId,
                                            @QueryParam("scope") final String scope, @QueryParam("state") final String state,
                                            @QueryParam("redirect_uri") String redirectUri) {
         RealmManager realmManager = new RealmManager(session);
-        RealmModel realm = realmManager.getRealm(realmId);
+        RealmModel realm = realmManager.getRealmByName(realmName);
 
         SocialProvider provider = SocialLoader.load(providerId);
         if (provider == null) {
@@ -223,7 +223,7 @@ public class SocialResource {
             AuthRequest authRequest = provider.getAuthUrl(config);
 
             RequestDetails socialRequest = RequestDetails.create(providerId)
-                    .putSocialAttributes(authRequest.getAttributes()).putClientAttribute("realmId", realmId)
+                    .putSocialAttributes(authRequest.getAttributes()).putClientAttribute("realm", realmName)
                     .putClientAttribute("clientId", clientId).putClientAttribute("scope", scope)
                     .putClientAttribute("state", state).putClientAttribute("redirectUri", redirectUri).build();
 
diff --git a/social/core/src/main/java/org/keycloak/social/AbstractOAuth2Provider.java b/social/core/src/main/java/org/keycloak/social/AbstractOAuth2Provider.java
new file mode 100644
index 0000000..ae8a0a5
--- /dev/null
+++ b/social/core/src/main/java/org/keycloak/social/AbstractOAuth2Provider.java
@@ -0,0 +1,90 @@
+package org.keycloak.social;
+
+import org.json.JSONObject;
+import org.keycloak.social.utils.SimpleHttp;
+
+import java.io.IOException;
+import java.util.UUID;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public abstract class AbstractOAuth2Provider implements SocialProvider {
+
+    private static final String AUTHORIZATION_CODE = "authorization_code";
+    private static final String ACCESS_TOKEN = "access_token";
+    private static final String CLIENT_ID = "client_id";
+    private static final String CLIENT_SECRET = "client_secret";
+    private static final String CODE = "code";
+    private static final String GRANT_TYPE = "grant_type";
+    private static final String REDIRECT_URI = "redirect_uri";
+    private static final String RESPONSE_TYPE = "response_type";
+    private static final String SCOPE = "scope";
+    private static final String STATE = "state";
+
+    private static final String TOKEN_REGEX = "access_token=([^&]+)";
+
+    @Override
+    public abstract String getId();
+
+    @Override
+    public abstract String getName();
+
+    protected abstract String getScope();
+
+    protected abstract String getAuthUrl();
+
+    protected abstract String getTokenUrl();
+
+    protected abstract SocialUser getProfile(String accessToken) throws SocialProviderException;
+
+    @Override
+    public AuthRequest getAuthUrl(SocialProviderConfig config) throws SocialProviderException {
+        String state = UUID.randomUUID().toString();
+
+        return AuthRequest.create(state, getAuthUrl()).setQueryParam(CLIENT_ID, config.getKey())
+                .setQueryParam(RESPONSE_TYPE, CODE).setQueryParam(SCOPE, getScope())
+                .setQueryParam(REDIRECT_URI, config.getCallbackUrl()).setQueryParam(STATE, state).setAttribute(STATE, state).build();
+    }
+
+    @Override
+    public SocialUser processCallback(SocialProviderConfig config, AuthCallback callback) throws SocialProviderException {
+        try {
+            String code = callback.getQueryParam(CODE);
+
+            if (!callback.getQueryParam(STATE).equals(callback.getAttribute(STATE))) {
+                throw new SocialProviderException("Invalid state");
+            }
+
+            String response = SimpleHttp.doPost(getTokenUrl()).param(CODE, code).param(CLIENT_ID, config.getKey())
+                    .param(CLIENT_SECRET, config.getSecret())
+                    .param(REDIRECT_URI, config.getCallbackUrl())
+                    .param(GRANT_TYPE, AUTHORIZATION_CODE).asString();
+
+            String accessToken;
+
+            if (response.startsWith("{")) {
+                accessToken = new JSONObject(response).getString(ACCESS_TOKEN);
+            } else {
+                Matcher matcher = Pattern.compile(TOKEN_REGEX).matcher(response);
+                if (matcher.find()) {
+                    accessToken = matcher.group(1);
+                } else {
+                    throw new SocialProviderException("Invalid response, could not find token");
+                }
+            }
+
+            return getProfile(accessToken);
+        } catch (IOException e) {
+            throw new SocialProviderException(e);
+        }
+    }
+
+    @Override
+    public String getRequestIdParamName() {
+        return STATE;
+    }
+
+}
diff --git a/social/facebook/pom.xml b/social/facebook/pom.xml
index fdfc7bf..78527e0 100755
--- a/social/facebook/pom.xml
+++ b/social/facebook/pom.xml
@@ -18,17 +18,6 @@
             <groupId>org.keycloak</groupId>
             <artifactId>keycloak-social-core</artifactId>
             <version>${project.version}</version>
-            <scope>provided</scope>
-        </dependency>
-        <dependency>
-            <groupId>org.jboss.resteasy</groupId>
-            <artifactId>resteasy-client</artifactId>
-            <scope>provided</scope>
-        </dependency>
-        <dependency>
-            <groupId>org.codehaus.jackson</groupId>
-            <artifactId>jackson-core-asl</artifactId>
-            <scope>provided</scope>
         </dependency>
     </dependencies>
 </project>
diff --git a/social/facebook/src/main/java/org/keycloak/social/facebook/FacebookProvider.java b/social/facebook/src/main/java/org/keycloak/social/facebook/FacebookProvider.java
index 8b911b2..dd2d7e3 100755
--- a/social/facebook/src/main/java/org/keycloak/social/facebook/FacebookProvider.java
+++ b/social/facebook/src/main/java/org/keycloak/social/facebook/FacebookProvider.java
@@ -1,149 +1,81 @@
 package org.keycloak.social.facebook;
 
-import org.jboss.resteasy.client.jaxrs.ResteasyClient;
-import org.jboss.resteasy.client.jaxrs.ResteasyClientBuilder;
-import org.keycloak.social.AuthCallback;
+import org.json.JSONObject;
+import org.keycloak.social.AbstractOAuth2Provider;
 import org.keycloak.social.AuthRequest;
-import org.keycloak.social.SocialProvider;
 import org.keycloak.social.SocialProviderConfig;
 import org.keycloak.social.SocialProviderException;
 import org.keycloak.social.SocialUser;
-
-import javax.ws.rs.client.Entity;
-import javax.ws.rs.core.Form;
-import javax.ws.rs.core.HttpHeaders;
-import javax.ws.rs.core.MediaType;
-import javax.ws.rs.core.Response;
-import javax.ws.rs.core.UriBuilder;
-import java.net.URI;
-import java.util.UUID;
+import org.keycloak.social.utils.SimpleHttp;
 
 /**
- * Social provider for Facebook
- *
- * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
  */
-public class FacebookProvider implements SocialProvider {
-
-    private static final String AUTHENTICATION_ENDPOINT_URL = "https://graph.facebook.com/oauth/authorize";
+public class FacebookProvider extends AbstractOAuth2Provider {
 
-    private static final String ACCESS_TOKEN_ENDPOINT_URL = "https://graph.facebook.com/oauth/access_token";
+    private static final String ID = "facebook";
+    private static final String NAME = "Facebook";
 
-    private static final String PROFILE_ENDPOINT_URL = "https://graph.facebook.com/me";
-
-    private static final String DEFAULT_RESPONSE_TYPE = "code";
+    private static final String AUTH_URL = "https://graph.facebook.com/oauth/authorize";
+    private static final String TOKEN_URL = "https://graph.facebook.com/oauth/access_token";
+    private static final String PROFILE_URL = "https://graph.facebook.com/me";
 
     private static final String DEFAULT_SCOPE = "email";
 
     @Override
     public String getId() {
-        return "facebook";
+        return ID;
     }
 
     @Override
-    public AuthRequest getAuthUrl(SocialProviderConfig config) throws SocialProviderException {
-        String state = UUID.randomUUID().toString();
-
-        String redirectUri = config.getCallbackUrl();
-        redirectUri = redirectUri.replace("//localhost", "//127.0.0.1");
-
-        return AuthRequest.create(state, AUTHENTICATION_ENDPOINT_URL).setQueryParam("client_id", config.getKey())
-                .setQueryParam("response_type", DEFAULT_RESPONSE_TYPE).setQueryParam("scope", DEFAULT_SCOPE)
-                .setQueryParam("redirect_uri", redirectUri).setQueryParam("state", state).setAttribute("state", state).build();
+    public String getName() {
+        return NAME;
     }
 
     @Override
-    public String getRequestIdParamName() {
-        return "state";
+    protected String getScope() {
+        return DEFAULT_SCOPE;
     }
 
     @Override
-    public String getName() {
-        return "Facebook";
+    protected String getAuthUrl() {
+        return AUTH_URL;
     }
 
     @Override
-    public SocialUser processCallback(SocialProviderConfig config, AuthCallback callback) throws SocialProviderException {
-        String code = callback.getQueryParam(DEFAULT_RESPONSE_TYPE);
+    protected String getTokenUrl() {
+        return TOKEN_URL;
+    }
 
+    @Override
+    protected SocialUser getProfile(String accessToken) throws SocialProviderException {
         try {
-            if (!callback.getQueryParam("state").equals(callback.getAttribute("state"))) {
-                throw new SocialProviderException("Invalid state");
-            }
-
-            ResteasyClient client = new ResteasyClientBuilder()
-                    .hostnameVerification(ResteasyClientBuilder.HostnameVerificationPolicy.ANY).build();
-
-            String accessToken = loadAccessToken(code, config, client);
+            JSONObject profile = SimpleHttp.doGet(PROFILE_URL).header("Authorization", "Bearer " + accessToken).asJson();
 
-            FacebookUser facebookUser = loadUser(accessToken, client);
+            SocialUser user = new SocialUser(profile.getString("id"));
 
-            SocialUser socialUser = new SocialUser(facebookUser.getId());
-            socialUser.setUsername(facebookUser.getUsername());
-
-            // This could happen with Facebook testing users
-            if (facebookUser.getUsername() == null || facebookUser.getUsername().length() == 0) {
-                socialUser.setUsername(facebookUser.getId());
+            user.setUsername(profile.getString("username"));
+            if (user.getUsername() == null || user.getUsername().length() == 0) {
+                user.setUsername(profile.getString("id"));
             }
 
-            socialUser.setEmail(facebookUser.getEmail());
-            socialUser.setLastName(facebookUser.getLastName());
-            socialUser.setFirstName(facebookUser.getFirstName());
+            user.setFirstName(profile.optString("first_name"));
+            user.setLastName(profile.optString("last_name"));
+            user.setEmail(profile.optString("email"));
 
-            return socialUser;
-        } catch (SocialProviderException spe) {
-            throw spe;
+            return user;
         } catch (Exception e) {
             throw new SocialProviderException(e);
         }
     }
 
-    protected String loadAccessToken(String code, SocialProviderConfig config, ResteasyClient client) throws SocialProviderException {
-        Form form = new Form();
-        form.param("grant_type", "authorization_code")
-                .param("code", code)
-                .param("client_id", config.getKey())
-                .param("client_secret", config.getSecret())
-                .param("redirect_uri", config.getCallbackUrl());
-
-        Response response = client.target(ACCESS_TOKEN_ENDPOINT_URL).request().post(Entity.form(form));
-
-        if (response.getStatus() != 200) {
-            String errorTokenResponse = response.readEntity(String.class);
-            throw new SocialProviderException("Access token request to Facebook failed. Status: " + response.getStatus() + ", response: " + errorTokenResponse);
-        }
-
-        String accessTokenResponse = response.readEntity(String.class);
-        return parseParameter(accessTokenResponse, "access_token");
-    }
-
-    protected FacebookUser loadUser(String accessToken, ResteasyClient client) throws SocialProviderException {
-        URI userDetailsUri = UriBuilder.fromUri(PROFILE_ENDPOINT_URL)
-                .queryParam("access_token", accessToken)
-                .queryParam("fields", "id,name,username,first_name,last_name,email")
-                .build();
-
-        Response response = client.target(userDetailsUri).request()
-                .header(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON)
-                .get();
-        if (response.getStatus() != 200) {
-            String errorTokenResponse = response.readEntity(String.class);
-            throw new SocialProviderException("Request to Facebook for obtaining user failed. Status: " + response.getStatus() + ", response: " + errorTokenResponse);
+    @Override
+    public AuthRequest getAuthUrl(SocialProviderConfig config) throws SocialProviderException {
+        if (config.getCallbackUrl().contains("//localhost")) {
+            String callbackUrl = config.getCallbackUrl().replace("//localhost", "//127.0.0.1");
+            config = new SocialProviderConfig(config.getKey(), config.getSecret(), callbackUrl);
         }
-
-        return response.readEntity(FacebookUser.class);
+        return super.getAuthUrl(config);
     }
 
-    // Parses value of given parameter from input string like "my_param=abcd&another_param=xyz"
-    private String parseParameter(String input, String paramName) {
-        int start = input.indexOf(paramName + "=");
-        if (start != -1) {
-            input = input.substring(start + paramName.length() + 1);
-            int end = input.indexOf("&");
-            return end==-1 ? input : input.substring(0, end);
-        } else {
-            throw new IllegalArgumentException("Parameter " + paramName + " not available in response " + input);
-        }
-
-    }
 }
diff --git a/social/github/pom.xml b/social/github/pom.xml
new file mode 100755
index 0000000..e75021d
--- /dev/null
+++ b/social/github/pom.xml
@@ -0,0 +1,23 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <artifactId>keycloak-social-parent</artifactId>
+        <groupId>org.keycloak</groupId>
+        <version>1.0-alpha-2-SNAPSHOT</version>
+        <relativePath>../pom.xml</relativePath>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+    <packaging>jar</packaging>
+
+    <artifactId>keycloak-social-github</artifactId>
+    <name>Keycloak Social GitHub</name>
+    <description/>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-social-core</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+    </dependencies>
+</project>
diff --git a/social/github/src/main/java/org/keycloak/social/github/GitHubProvider.java b/social/github/src/main/java/org/keycloak/social/github/GitHubProvider.java
new file mode 100755
index 0000000..9ea0096
--- /dev/null
+++ b/social/github/src/main/java/org/keycloak/social/github/GitHubProvider.java
@@ -0,0 +1,67 @@
+package org.keycloak.social.github;
+
+import org.json.JSONObject;
+import org.keycloak.social.AbstractOAuth2Provider;
+import org.keycloak.social.AuthRequest;
+import org.keycloak.social.SocialProviderConfig;
+import org.keycloak.social.SocialProviderException;
+import org.keycloak.social.SocialUser;
+import org.keycloak.social.utils.SimpleHttp;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class GitHubProvider extends AbstractOAuth2Provider {
+
+    private static final String ID = "github";
+    private static final String NAME = "GitHub";
+
+    private static final String AUTH_URL = "https://github.com/login/oauth/authorize";
+    private static final String TOKEN_URL = "https://github.com/login/oauth/access_token";
+    private static final String PROFILE_URL = "https://api.github.com/user";
+
+    private static final String DEFAULT_SCOPE = "user:email";
+
+    @Override
+    public String getId() {
+        return ID;
+    }
+
+    @Override
+    public String getName() {
+        return NAME;
+    }
+
+    @Override
+    protected String getScope() {
+        return DEFAULT_SCOPE;
+    }
+
+    @Override
+    protected String getAuthUrl() {
+        return AUTH_URL;
+    }
+
+    @Override
+    protected String getTokenUrl() {
+        return TOKEN_URL;
+    }
+
+    @Override
+    protected SocialUser getProfile(String accessToken) throws SocialProviderException {
+        try {
+            JSONObject profile = SimpleHttp.doGet(PROFILE_URL).header("Authorization", "Bearer " + accessToken).asJson();
+
+            SocialUser user = new SocialUser(profile.get("id").toString());
+
+            user.setUsername(profile.getString("login"));
+            user.setFirstName(profile.optString("name"));
+            user.setEmail(profile.optString("email"));
+
+            return user;
+        } catch (Exception e) {
+            throw new SocialProviderException(e);
+        }
+    }
+
+}
diff --git a/social/github/src/main/resources/META-INF/services/org.keycloak.social.SocialProvider b/social/github/src/main/resources/META-INF/services/org.keycloak.social.SocialProvider
new file mode 100644
index 0000000..f9e6b0c
--- /dev/null
+++ b/social/github/src/main/resources/META-INF/services/org.keycloak.social.SocialProvider
@@ -0,0 +1 @@
+org.keycloak.social.github.GitHubProvider
diff --git a/social/google/pom.xml b/social/google/pom.xml
index 12ecb81..f5b5681 100755
--- a/social/google/pom.xml
+++ b/social/google/pom.xml
@@ -19,5 +19,4 @@
             <version>${project.version}</version>
         </dependency>
     </dependencies>
-
 </project>
diff --git a/social/google/src/main/java/org/keycloak/social/google/GoogleProvider.java b/social/google/src/main/java/org/keycloak/social/google/GoogleProvider.java
index 87ea93f..2db4a9c 100755
--- a/social/google/src/main/java/org/keycloak/social/google/GoogleProvider.java
+++ b/social/google/src/main/java/org/keycloak/social/google/GoogleProvider.java
@@ -22,67 +22,54 @@
 package org.keycloak.social.google;
 
 import org.json.JSONObject;
-import org.keycloak.social.AuthCallback;
-import org.keycloak.social.AuthRequest;
+import org.keycloak.social.AbstractOAuth2Provider;
 import org.keycloak.social.utils.SimpleHttp;
-import org.keycloak.social.SocialProvider;
-import org.keycloak.social.SocialProviderConfig;
 import org.keycloak.social.SocialProviderException;
 import org.keycloak.social.SocialUser;
 
-import java.util.UUID;
-
 /**
  * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
  */
-public class GoogleProvider implements SocialProvider {
-
-    private static final String DEFAULT_RESPONSE_TYPE = "code";
+public class GoogleProvider extends AbstractOAuth2Provider {
 
-    private static final String AUTH_PATH = "https://accounts.google.com/o/oauth2/auth";
+    private static final String ID = "google";
+    private static final String NAME = "Google";
 
-    private static final String TOKEN_PATH = "https://accounts.google.com/o/oauth2/token";
-
-    private static final String PROFILE_PATH = "https://www.googleapis.com/plus/v1/people/me/openIdConnect";
+    private static final String AUTH_URL = "https://accounts.google.com/o/oauth2/auth";
+    private static final String TOKEN_URL = "https://accounts.google.com/o/oauth2/token";
+    private static final String PROFILE_URL = "https://www.googleapis.com/plus/v1/people/me/openIdConnect";
 
     private static final String DEFAULT_SCOPE = "openid profile email";
 
     @Override
     public String getId() {
-        return "google";
+        return ID;
     }
 
     @Override
-    public AuthRequest getAuthUrl(SocialProviderConfig config) throws SocialProviderException {
-        String state = UUID.randomUUID().toString();
+    public String getName() {
+        return NAME;
+    }
 
-        return AuthRequest.create(state, AUTH_PATH).setQueryParam("client_id", config.getKey())
-                .setQueryParam("response_type", DEFAULT_RESPONSE_TYPE).setQueryParam("scope", DEFAULT_SCOPE)
-                .setQueryParam("redirect_uri", config.getCallbackUrl()).setQueryParam("state", state).setAttribute("state", state).build();
+    @Override
+    protected String getScope() {
+        return DEFAULT_SCOPE;
     }
 
     @Override
-    public String getName() {
-        return "Google";
+    protected String getAuthUrl() {
+        return AUTH_URL;
     }
 
     @Override
-    public SocialUser processCallback(SocialProviderConfig config, AuthCallback callback) throws SocialProviderException {
-        String code = callback.getQueryParam(DEFAULT_RESPONSE_TYPE);
+    protected String getTokenUrl() {
+        return TOKEN_URL;
+    }
 
+    @Override
+    protected SocialUser getProfile(String accessToken) throws SocialProviderException {
         try {
-            if (!callback.getQueryParam("state").equals(callback.getAttribute("state"))) {
-                throw new SocialProviderException("Invalid state");
-            }
-
-            JSONObject token = SimpleHttp.doPost(TOKEN_PATH).param("code", code).param("client_id", config.getKey())
-                    .param("client_secret", config.getSecret())
-                    .param("redirect_uri", config.getCallbackUrl())
-                    .param("grant_type", "authorization_code").asJson();
-
-            String accessToken = token.getString("access_token");
-
-            JSONObject profile = SimpleHttp.doGet(PROFILE_PATH).header("Authorization", "Bearer " + accessToken).asJson();
+            JSONObject profile = SimpleHttp.doGet(PROFILE_URL).header("Authorization", "Bearer " + accessToken).asJson();
 
             SocialUser user = new SocialUser(profile.getString("sub"));
 
@@ -98,9 +85,4 @@ public class GoogleProvider implements SocialProvider {
         }
     }
 
-    @Override
-    public String getRequestIdParamName() {
-        return "state";
-    }
-
 }

social/pom.xml 1(+1 -0)

diff --git a/social/pom.xml b/social/pom.xml
index 1061eda..5d4edd7 100755
--- a/social/pom.xml
+++ b/social/pom.xml
@@ -15,6 +15,7 @@
 
     <modules>
         <module>core</module>
+        <module>github</module>
         <module>google</module>
         <module>twitter</module>
         <module>facebook</module>
diff --git a/testsuite/integration/pom.xml b/testsuite/integration/pom.xml
index e228bbe..acd2316 100755
--- a/testsuite/integration/pom.xml
+++ b/testsuite/integration/pom.xml
@@ -75,6 +75,11 @@
         </dependency>
         <dependency>
             <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-social-github</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
             <artifactId>keycloak-social-google</artifactId>
             <version>${project.version}</version>
         </dependency>