keycloak-uncached

Merge pull request #285 from stianst/master Add user friendly

3/12/2014 10:52:28 AM

Changes

keycloak.json 8(+8 -0)

Details

diff --git a/core/src/main/java/org/keycloak/representations/idm/SocialLinkRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/SocialLinkRepresentation.java
index a6e1838..8203261 100644
--- a/core/src/main/java/org/keycloak/representations/idm/SocialLinkRepresentation.java
+++ b/core/src/main/java/org/keycloak/representations/idm/SocialLinkRepresentation.java
@@ -6,6 +6,7 @@ package org.keycloak.representations.idm;
 public class SocialLinkRepresentation {
 
     protected String socialProvider;
+    protected String socialUserId;
     protected String socialUsername;
 
     public String getSocialProvider() {
@@ -16,6 +17,14 @@ public class SocialLinkRepresentation {
         this.socialProvider = socialProvider;
     }
 
+    public String getSocialUserId() {
+        return socialUserId;
+    }
+
+    public void setSocialUserId(String socialUserId) {
+        this.socialUserId = socialUserId;
+    }
+
     public String getSocialUsername() {
         return socialUsername;
     }
diff --git a/core/src/main/java/org/keycloak/representations/idm/SocialMappingRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/SocialMappingRepresentation.java
index 57dd874..c4d852c 100644
--- a/core/src/main/java/org/keycloak/representations/idm/SocialMappingRepresentation.java
+++ b/core/src/main/java/org/keycloak/representations/idm/SocialMappingRepresentation.java
@@ -32,9 +32,10 @@ public class SocialMappingRepresentation {
         return socialLinks;
     }
 
-    public SocialLinkRepresentation socialLink(String socialProvider, String socialUsername) {
+    public SocialLinkRepresentation socialLink(String socialProvider, String socialUserId, String socialUsername) {
         SocialLinkRepresentation link = new SocialLinkRepresentation();
         link.setSocialProvider(socialProvider);
+        link.setSocialUserId(socialUserId);
         link.setSocialUsername(socialUsername);
         if (socialLinks == null) socialLinks = new ArrayList<SocialLinkRepresentation>();
         socialLinks.add(link);
diff --git a/docbook/reference/en/en-US/modules/social-facebook.xml b/docbook/reference/en/en-US/modules/social-facebook.xml
index bc87171..6e5e832 100644
--- a/docbook/reference/en/en-US/modules/social-facebook.xml
+++ b/docbook/reference/en/en-US/modules/social-facebook.xml
@@ -40,10 +40,4 @@
             </para>
         </listitem>
     </orderedlist>
-    <tip>
-        <para>
-            Facebook doesn't allow <literal>localhost</literal> in the redirect URI. To test on a local server
-            replace <literal>localhost</literal> with <literal>127.0.0.1</literal>.
-        </para>
-    </tip>
 </section>
\ No newline at end of file
diff --git a/examples/demo-template/customer-app-js/src/main/webapp/customers/view.html b/examples/demo-template/customer-app-js/src/main/webapp/customers/view.html
index 36ef79f..ee38261 100755
--- a/examples/demo-template/customer-app-js/src/main/webapp/customers/view.html
+++ b/examples/demo-template/customer-app-js/src/main/webapp/customers/view.html
@@ -1,25 +1,52 @@
 <html>
 <head>
     <title>Customer View Page</title>
-    <script src="/auth/js/keycloak.js"></script>
+    <script src="http://localhost:8081/auth/js/keycloak.js"></script>
 </head>
 <body bgcolor="#E3F6CE">
 
 <p>Goto: <a href="#" onclick="keycloak.logout()">logout</a></p>
-User <b id="username"></b> made this request.
+User <b id="subject"></b> made this request.
+<p><b>User details (from <span id="profileType"></span>)</b></p>
+<p>Username: <span id="username"></span></p>
+<p>Email: <span id="email"></span></p>
+<p>Full Name: <span id="name"></span></p>
+<p>First: <span id="givenName"></span></p>
+<p>Last: <span id="familyName"></span></p>
+
 <h2>Customer Listing</h2>
 <div id="customers"></div>
 
 <script>
     var keycloak = Keycloak({
         clientId: 'customer-portal',
-        clientSecret: 'password',
         realm: 'demo',
         onload: 'login-required'
     });
 
     var loadData = function () {
-        document.getElementById('username').innerText = keycloak.username;
+        document.getElementById('subject').innerText = keycloak.subject;
+
+        console.debug(keycloak.idToken);
+        if (keycloak.idToken) {
+            document.getElementById('profileType').innerText = 'IDToken';
+            document.getElementById('username').innerText = keycloak.idToken.preferred_username;
+            document.getElementById('email').innerText = keycloak.idToken.email;
+            document.getElementById('name').innerText = keycloak.idToken.name;
+            document.getElementById('givenName').innerText = keycloak.idToken.given_name;
+            document.getElementById('familyName').innerText = keycloak.idToken.family_name;
+        } else {
+            keycloak.loadUserProfile(function() {
+                document.getElementById('profileType').innerText = 'Account Service';
+                document.getElementById('username').innerText = keycloak.profile.username;
+                document.getElementById('email').innerText = keycloak.profile.email;
+                document.getElementById('name').innerText = keycloak.profile.firstName + ' ' + keycloak.profile.lastName;
+                document.getElementById('givenName').innerText = keycloak.profile.firstName;
+                document.getElementById('familyName').innerText = keycloak.profile.lastName;
+            }, function() {
+                document.getElementById('profileType').innerText = 'Failed to retrieve user details. Please enable claims or account role';
+            });
+        }
 
         var url = 'http://localhost:8080/database/customers';
 
@@ -50,12 +77,11 @@ User <b id="username"></b> made this request.
 
     };
 
-
-
     var reloadData = function () {
         keycloak.onValidAccessToken(loadData, loadFailure);
     }
-    keycloak.init(loadData);
+
+    keycloak.init(loadData, loadFailure);
 
 </script>
 
diff --git a/examples/test-cordova.json b/examples/test-cordova.json
new file mode 100644
index 0000000..9de2ee3
--- /dev/null
+++ b/examples/test-cordova.json
@@ -0,0 +1,27 @@
+{
+    "id": "test",
+    "realm": "test",
+    "enabled": true,
+    "sslNotRequired": true,
+    "privateKey": "MIICXAIBAAKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQABAoGAfmO8gVhyBxdqlxmIuglbz8bcjQbhXJLR2EoS8ngTXmN1bo2L90M0mUKSdc7qF10LgETBzqL8jYlQIbt+e6TH8fcEpKCjUlyq0Mf/vVbfZSNaVycY13nTzo27iPyWQHK5NLuJzn1xvxxrUeXI6A2WFpGEBLbHjwpx5WQG9A+2scECQQDvdn9NE75HPTVPxBqsEd2z10TKkl9CZxu10Qby3iQQmWLEJ9LNmy3acvKrE3gMiYNWb6xHPKiIqOR1as7L24aTAkEAtyvQOlCvr5kAjVqrEKXalj0Tzewjweuxc0pskvArTI2Oo070h65GpoIKLc9jf+UA69cRtquwP93aZKtW06U8dQJAF2Y44ks/mK5+eyDqik3koCI08qaC8HYq2wVl7G2QkJ6sbAaILtcvD92ToOvyGyeE0flvmDZxMYlvaZnaQ0lcSQJBAKZU6umJi3/xeEbkJqMfeLclD27XGEFoPeNrmdx0q10Azp4NfJAY+Z8KRyQCR2BEG+oNitBOZ+YXF9KCpH3cdmECQHEigJhYg+ykOvr1aiZUMFT72HU0jnmQe2FVekuG+LJUt2Tm7GtMjTFoGpf0JwrVuZN39fOYAlo+nTixgeW7X8Y=",
+    "publicKey": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
+    "requiredCredentials": [ "password" ],
+    "users" : [
+        {
+            "username" : "test",
+            "enabled": true,
+            "email" : "test-user@localhost",
+            "credentials" : [
+                { "type" : "password",
+                    "value" : "test" }
+            ]
+        }
+    ],
+    "applications": [
+        {
+            "name": "test",
+            "enabled": true,
+            "secret": "password"
+         }
+    ]
+}
\ No newline at end of file
diff --git a/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/model/AccountSocialBean.java b/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/model/AccountSocialBean.java
index ed94d69..2fbe8c6 100644
--- a/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/model/AccountSocialBean.java
+++ b/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/model/AccountSocialBean.java
@@ -33,22 +33,22 @@ public class AccountSocialBean {
             for (SocialProvider provider : SocialLoader.load()) {
                 String socialProviderId = provider.getId();
                 if (socialConfig.containsKey(socialProviderId + ".key")) {
-                    String socialUsername = getSocialUsername(userSocialLinks, socialProviderId);
+                    SocialLinkModel socialLink = getSocialLink(userSocialLinks, socialProviderId);
 
-                    String action = socialUsername!=null ? "remove" : "add";
+                    String action = socialLink != null ? "remove" : "add";
                     String actionUrl = UriBuilder.fromUri(accountSocialUpdateUri).queryParam("action", action).queryParam("provider_id", socialProviderId).build().toString();
 
-                    SocialLinkEntry entry = new SocialLinkEntry(socialProviderId, provider.getName(), socialUsername, actionUrl);
+                    SocialLinkEntry entry = new SocialLinkEntry(socialLink, provider.getName(), actionUrl);
                     this.socialLinks.add(entry);
                 }
             }
         }
     }
 
-    private String getSocialUsername(Set<SocialLinkModel> userSocialLinks, String socialProviderId) {
+    private SocialLinkModel getSocialLink(Set<SocialLinkModel> userSocialLinks, String socialProviderId) {
         for (SocialLinkModel link : userSocialLinks) {
             if (socialProviderId.equals(link.getSocialProvider())) {
-                return link.getSocialUsername();
+                return link;
             }
         }
         return null;
@@ -60,32 +60,34 @@ public class AccountSocialBean {
 
     public class SocialLinkEntry {
 
-        private final String providerId;
+        private SocialLinkModel link;
         private final String providerName;
-        private final String socialUsername;
         private final String actionUrl;
 
-        public SocialLinkEntry(String providerId, String providerName, String socialUsername, String actionUrl) {
-            this.providerId = providerId;
+        public SocialLinkEntry(SocialLinkModel link, String providerName, String actionUrl) {
+            this.link = link;
             this.providerName = providerName;
-            this.socialUsername = socialUsername!=null ? socialUsername : "";
             this.actionUrl = actionUrl;
         }
 
         public String getProviderId() {
-            return providerId;
+            return link != null ? link.getSocialProvider() : null;
         }
 
         public String getProviderName() {
             return providerName;
         }
 
+        public String getSocialUserId() {
+            return link != null ? link.getSocialUserId() : null;
+        }
+
         public String getSocialUsername() {
-            return socialUsername;
+            return link != null ? link.getSocialUsername() : null;
         }
 
         public boolean isConnected() {
-            return !socialUsername.isEmpty();
+            return link != null;
         }
 
         public String getActionUrl() {
diff --git a/forms/common-themes/src/main/resources/theme/account/base/social.ftl b/forms/common-themes/src/main/resources/theme/account/base/social.ftl
index a1941de..e238248 100644
--- a/forms/common-themes/src/main/resources/theme/account/base/social.ftl
+++ b/forms/common-themes/src/main/resources/theme/account/base/social.ftl
@@ -11,16 +11,16 @@
         <#list social.links as socialLink>
             <div class="form-group">
                 <div class="col-sm-2 col-md-2">
-                    <label for="${socialLink.providerId}" class="control-label">${socialLink.providerName}</label>
+                    <label for="${socialLink.providerId!}" class="control-label">${socialLink.providerName!}</label>
                 </div>
                 <div class="col-sm-5 col-md-5">
-                    <input disabled="true" class="form-control" value="${socialLink.socialUsername}">
+                    <input disabled="true" class="form-control" value="${socialLink.socialUsername!}">
                 </div>
                 <div class="col-sm-5 col-md-5">
                     <#if socialLink.connected>
-                        <a href="${socialLink.actionUrl}" type="submit" class="btn btn-primary btn-lg">Remove ${socialLink.providerName}</a>
+                        <a href="${socialLink.actionUrl}" type="submit" class="btn btn-primary btn-lg">Remove ${socialLink.providerName!}</a>
                     <#else>
-                        <a href="${socialLink.actionUrl}" type="submit" class="btn btn-primary btn-lg">Add ${socialLink.providerName}</a>
+                        <a href="${socialLink.actionUrl}" type="submit" class="btn btn-primary btn-lg">Add ${socialLink.providerName!}</a>
                     </#if>
                 </div>
             </div>
diff --git a/forms/login-api/src/main/java/org/keycloak/login/LoginForms.java b/forms/login-api/src/main/java/org/keycloak/login/LoginForms.java
index 2eb3704..a02df45 100755
--- a/forms/login-api/src/main/java/org/keycloak/login/LoginForms.java
+++ b/forms/login-api/src/main/java/org/keycloak/login/LoginForms.java
@@ -43,6 +43,8 @@ public interface LoginForms {
 
     public LoginForms setClient(ClientModel client);
 
+    public LoginForms setQueryParams(MultivaluedMap<String, String> queryParams);
+
     public LoginForms setFormData(MultivaluedMap<String, String> formData);
 
     public LoginForms setStatus(Response.Status status);
diff --git a/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/FreeMarkerLoginForms.java b/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/FreeMarkerLoginForms.java
index 69e17fc..a91aaec 100755
--- a/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/FreeMarkerLoginForms.java
+++ b/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/FreeMarkerLoginForms.java
@@ -51,6 +51,7 @@ public class FreeMarkerLoginForms implements LoginForms {
     private Response.Status status = Response.Status.OK;
     private List<RoleModel> realmRolesRequested;
     private MultivaluedMap<String, RoleModel> resourceRolesRequested;
+    private MultivaluedMap<String, String> queryParams;
 
     public static enum MessageType {SUCCESS, WARNING, ERROR}
 
@@ -114,7 +115,7 @@ public class FreeMarkerLoginForms implements LoginForms {
     }
 
     private Response createResponse(LoginFormsPages page) {
-        MultivaluedMap<String, String> queryParameterMap = uriInfo.getQueryParameters();
+        MultivaluedMap<String, String> queryParameterMap = queryParams != null ? queryParams : uriInfo.getQueryParameters();
 
         String requestURI = uriInfo.getBaseUri().getPath();
         UriBuilder uriBuilder = UriBuilder.fromUri(requestURI);
@@ -276,4 +277,9 @@ public class FreeMarkerLoginForms implements LoginForms {
         return this;
     }
 
+    @Override
+    public LoginForms setQueryParams(MultivaluedMap<String, String> queryParams) {
+        this.queryParams = queryParams;
+        return this;
+    }
 }
diff --git a/integration/js/src/main/resources/META-INF/resources/js/keycloak.js b/integration/js/src/main/resources/META-INF/resources/js/keycloak.js
index 5253663..b3a6af4 100755
--- a/integration/js/src/main/resources/META-INF/resources/js/keycloak.js
+++ b/integration/js/src/main/resources/META-INF/resources/js/keycloak.js
@@ -194,13 +194,22 @@ var Keycloak = function (options) {
         if (token) {
             window.oauth.token = token;
             kc.token = token;
-
             kc.tokenParsed = JSON.parse(atob(token.split('.')[1]));
             kc.authenticated = true;
             kc.subject = kc.tokenParsed.sub;
             kc.realmAccess = kc.tokenParsed.realm_access;
             kc.resourceAccess = kc.tokenParsed.resource_access;
 
+            for (var i = 0; i < idTokenProperties.length; i++) {
+                var n = idTokenProperties[i];
+                if (kc.tokenParsed[n]) {
+                    if (!kc.idToken) {
+                        kc.idToken = {};
+                    }
+                    kc.idToken[n] = kc.tokenParsed[n];
+                }
+            }
+
             setTimeout(function() {
                 successCallback && successCallback({ authenticated: kc.authenticated, subject: kc.subject });
             }, 0);
@@ -260,6 +269,35 @@ var Keycloak = function (options) {
         var uuid = s.join('');
         return uuid;
     }
+    
+    var idTokenProperties = [
+        "name", 
+        "given_name", 
+        "family_name", 
+        "middle_name", 
+        "nickname", 
+        "preferred_username", 
+        "profile", 
+        "picture", 
+        "website", 
+        "email", 
+        "email_verified", 
+        "gender", 
+        "birthdate", 
+        "zoneinfo", 
+        "locale", 
+        "phone_number", 
+        "phone_number_verified", 
+        "address", 
+        "updated_at", 
+        "formatted", 
+        "street_address", 
+        "locality", 
+        "region", 
+        "postal_code", 
+        "country", 
+        "claims_locales"
+    ]
 }
 
 window.oauth = (function () {
diff --git a/integration/js/src/main/resources/META-INF/resources/js/test.html b/integration/js/src/main/resources/META-INF/resources/js/test.html
new file mode 100644
index 0000000..fb20e03
--- /dev/null
+++ b/integration/js/src/main/resources/META-INF/resources/js/test.html
@@ -0,0 +1,24 @@
+<html>
+<head>
+    <script src="http://192.168.0.16/js/keycloak.js"></script>
+</head>
+<body>
+<button onclick="keycloak.login()">Login</button>
+
+<script>
+    var keycloak = Keycloak({ realm: 'test', clientId: 'test', clientSecret: 'password' });
+
+    keycloak.init(function () {
+        console.debug('Token: ' + keycloak.tokenParsed);
+        console.debug('Realm access: ' + keycloak.realmAccess);
+        console.debug('Resource access: ' + keycloak.resourceAccess);
+
+        keycloak.loadUserProfile(function (profile) {
+            console.debug(profile);
+        }, function (error) {
+            console.debug(error);
+        })
+    });
+</script>
+</body>
+</html>

keycloak.json 8(+8 -0)

diff --git a/keycloak.json b/keycloak.json
new file mode 100644
index 0000000..35e49cd
--- /dev/null
+++ b/keycloak.json
@@ -0,0 +1,8 @@
+{
+  "realm" : "test",
+  "realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDddHi8/MoYNtdafydQ+e4P0qrvClCW0o/x9fAbZ09dET/VBZCU28M54LmgJT7snXfreyYBpaQpHkW52/tyuEcJ5KO28LIcvObDQFDq3Z7esrovHl4NRETJBF9Xqwt+XLTZF6m37fYzaUK6MVJzUHP9qmu90LYyyvQ+hBJD0GSw1QIDAQAB",
+  "auth-server-url" : "http://localhost:8081/auth",
+  "ssl-not-required" : true,
+  "resource" : "test",
+  "public-client" : true
+}
\ No newline at end of file
diff --git a/model/api/src/main/java/org/keycloak/models/SocialLinkModel.java b/model/api/src/main/java/org/keycloak/models/SocialLinkModel.java
index 742da11..76e8929 100755
--- a/model/api/src/main/java/org/keycloak/models/SocialLinkModel.java
+++ b/model/api/src/main/java/org/keycloak/models/SocialLinkModel.java
@@ -5,20 +5,22 @@ package org.keycloak.models;
  */
 public class SocialLinkModel {
 
-    private String socialUsername;
+    private String socialUserId;
     private String socialProvider;
+    private String socialUsername;
 
-    public SocialLinkModel(String socialProvider, String socialUsername) {
-        this.socialUsername = socialUsername;
+    public SocialLinkModel(String socialProvider, String socialUserId, String socialUsername) {
+        this.socialUserId = socialUserId;
         this.socialProvider = socialProvider;
+        this.socialUsername = socialUsername;
     }
 
-    public String getSocialUsername() {
-        return socialUsername;
+    public String getSocialUserId() {
+        return socialUserId;
     }
 
-    public void setSocialUsername(String socialUsername) {
-        this.socialUsername = socialUsername;
+    public void setSocialUserId(String socialUserId) {
+        this.socialUserId = socialUserId;
     }
 
     public String getSocialProvider() {
@@ -28,4 +30,12 @@ public class SocialLinkModel {
     public void setSocialProvider(String socialProvider) {
         this.socialProvider = socialProvider;
     }
+
+    public String getSocialUsername() {
+        return socialUsername;
+    }
+
+    public void setSocialUsername(String socialUsername) {
+        this.socialUsername = socialUsername;
+    }
 }
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/SocialLinkEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/SocialLinkEntity.java
index 8d53f14..84db8f6 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/SocialLinkEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/SocialLinkEntity.java
@@ -16,7 +16,7 @@ import org.hibernate.annotations.GenericGenerator;
 @NamedQueries({
         @NamedQuery(name="findSocialLinkByUser", query="select link from SocialLinkEntity link where link.user = :user"),
         @NamedQuery(name="findSocialLinkByUserAndProvider", query="select link from SocialLinkEntity link where link.user = :user and link.socialProvider = :socialProvider"),
-        @NamedQuery(name="findUserByLinkAndRealm", query="select link.user from SocialLinkEntity link where link.realm = :realm and link.socialProvider = :socialProvider and link.socialUsername = :socialUsername")
+        @NamedQuery(name="findUserByLinkAndRealm", query="select link.user from SocialLinkEntity link where link.realm = :realm and link.socialProvider = :socialProvider and link.socialUserId = :socialUserId")
 })
 @Entity
 public class SocialLinkEntity {
@@ -32,6 +32,7 @@ public class SocialLinkEntity {
     protected RealmEntity realm;
 
     protected String socialProvider;
+    protected String socialUserId;
     protected String socialUsername;
 
     public String getId() {
@@ -58,6 +59,14 @@ public class SocialLinkEntity {
         this.socialProvider = socialProvider;
     }
 
+    public String getSocialUserId() {
+        return socialUserId;
+    }
+
+    public void setSocialUserId(String socialUserId) {
+        this.socialUserId = socialUserId;
+    }
+
     public String getSocialUsername() {
         return socialUsername;
     }
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
index 986e565..465f717 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
@@ -543,13 +543,13 @@ public class RealmAdapter implements RealmModel {
         TypedQuery<UserEntity> query = em.createNamedQuery("findUserByLinkAndRealm", UserEntity.class);
         query.setParameter("realm", realm);
         query.setParameter("socialProvider", socialLink.getSocialProvider());
-        query.setParameter("socialUsername", socialLink.getSocialUsername());
+        query.setParameter("socialUserId", socialLink.getSocialUserId());
         List<UserEntity> results = query.getResultList();
         if (results.isEmpty()) {
             return null;
         } else if (results.size() > 1) {
             throw new IllegalStateException("More results found for socialProvider=" + socialLink.getSocialProvider() +
-                    ", socialUsername=" + socialLink.getSocialUsername() + ", results=" + results);
+                    ", socialUserId=" + socialLink.getSocialUserId() + ", results=" + results);
         } else {
             UserEntity user = results.get(0);
             return new UserAdapter(user);
@@ -563,7 +563,7 @@ public class RealmAdapter implements RealmModel {
         List<SocialLinkEntity> results = query.getResultList();
         Set<SocialLinkModel> set = new HashSet<SocialLinkModel>();
         for (SocialLinkEntity entity : results) {
-            set.add(new SocialLinkModel(entity.getSocialProvider(), entity.getSocialUsername()));
+            set.add(new SocialLinkModel(entity.getSocialProvider(), entity.getSocialUserId(), entity.getSocialUsername()));
         }
         return set;
     }
@@ -571,7 +571,7 @@ public class RealmAdapter implements RealmModel {
     @Override
     public SocialLinkModel getSocialLink(UserModel user, String socialProvider) {
         SocialLinkEntity entity = findSocialLink(user, socialProvider);
-        return (entity != null) ? new SocialLinkModel(entity.getSocialProvider(), entity.getSocialUsername()) : null;
+        return (entity != null) ? new SocialLinkModel(entity.getSocialProvider(), entity.getSocialUserId(), entity.getSocialUsername()) : null;
     }
 
     @Override
@@ -579,6 +579,7 @@ public class RealmAdapter implements RealmModel {
         SocialLinkEntity entity = new SocialLinkEntity();
         entity.setRealm(realm);
         entity.setSocialProvider(socialLink.getSocialProvider());
+        entity.setSocialUserId(socialLink.getSocialUserId());
         entity.setSocialUsername(socialLink.getSocialUsername());
         entity.setUser(((UserAdapter) user).getUser());
         em.persist(entity);
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java
index d2b5a33..90910f9 100755
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java
@@ -853,7 +853,7 @@ public class RealmAdapter extends AbstractMongoAdapter<RealmEntity> implements R
     public UserModel getUserBySocialLink(SocialLinkModel socialLink) {
         DBObject query = new QueryBuilder()
                 .and("socialLinks.socialProvider").is(socialLink.getSocialProvider())
-                .and("socialLinks.socialUsername").is(socialLink.getSocialUsername())
+                .and("socialLinks.socialUserId").is(socialLink.getSocialUserId())
                 .and("realmId").is(getId())
                 .get();
         UserEntity userEntity = getMongoStore().loadSingleEntity(UserEntity.class, query, invocationContext);
@@ -871,7 +871,7 @@ public class RealmAdapter extends AbstractMongoAdapter<RealmEntity> implements R
 
         Set<SocialLinkModel> result = new HashSet<SocialLinkModel>();
         for (SocialLinkEntity socialLinkEntity : linkEntities) {
-            SocialLinkModel model = new SocialLinkModel(socialLinkEntity.getSocialProvider(), socialLinkEntity.getSocialUsername());
+            SocialLinkModel model = new SocialLinkModel(socialLinkEntity.getSocialProvider(), socialLinkEntity.getSocialUserId(), socialLinkEntity.getSocialUsername());
             result.add(model);
         }
         return result;
@@ -880,7 +880,7 @@ public class RealmAdapter extends AbstractMongoAdapter<RealmEntity> implements R
     @Override
     public SocialLinkModel getSocialLink(UserModel user, String socialProvider) {
         SocialLinkEntity socialLinkEntity = findSocialLink(user, socialProvider);
-        return socialLinkEntity!=null ? new SocialLinkModel(socialLinkEntity.getSocialProvider(), socialLinkEntity.getSocialUsername()) : null;
+        return socialLinkEntity!=null ? new SocialLinkModel(socialLinkEntity.getSocialProvider(), socialLinkEntity.getSocialUserId(), socialLinkEntity.getSocialUsername()) : null;
     }
 
     @Override
@@ -888,6 +888,7 @@ public class RealmAdapter extends AbstractMongoAdapter<RealmEntity> implements R
         UserEntity userEntity = ((UserAdapter)user).getUser();
         SocialLinkEntity socialLinkEntity = new SocialLinkEntity();
         socialLinkEntity.setSocialProvider(socialLink.getSocialProvider());
+        socialLinkEntity.setSocialUserId(socialLink.getSocialUserId());
         socialLinkEntity.setSocialUsername(socialLink.getSocialUsername());
 
         getMongoStore().pushItemToList(userEntity, "socialLinks", socialLinkEntity, true, invocationContext);
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/SocialLinkEntity.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/SocialLinkEntity.java
index 85ae5c0..85afa11 100644
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/SocialLinkEntity.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/SocialLinkEntity.java
@@ -8,10 +8,20 @@ import org.keycloak.models.mongo.api.MongoField;
  */
 public class SocialLinkEntity implements MongoEntity {
 
+    private String socialUserId;
     private String socialUsername;
     private String socialProvider;
 
     @MongoField
+    public String getSocialUserId() {
+        return socialUserId;
+    }
+
+    public void setSocialUserId(String socialUserId) {
+        this.socialUserId = socialUserId;
+    }
+
+    @MongoField
     public String getSocialUsername() {
         return socialUsername;
     }
@@ -37,9 +47,9 @@ public class SocialLinkEntity implements MongoEntity {
         SocialLinkEntity that = (SocialLinkEntity) o;
 
         if (socialProvider != null && (that.socialProvider == null || !socialProvider.equals(that.socialProvider))) return false;
-        if (socialUsername != null && (that.socialUsername == null || !socialUsername.equals(that.socialUsername))) return false;
+        if (socialUserId != null && (that.socialUserId == null || !socialUserId.equals(that.socialUserId))) return false;
         if (socialProvider == null && that.socialProvider != null)return false;
-        if (socialUsername == null && that.socialUsername != null) return false;
+        if (socialUserId == null && that.socialUserId != null) return false;
 
         return true;
     }
@@ -47,7 +57,7 @@ public class SocialLinkEntity implements MongoEntity {
     @Override
     public int hashCode() {
         int code = 1;
-        if (socialUsername != null) {
+        if (socialUserId != null) {
             code = code * 13;
         }
         if (socialProvider != null) {
diff --git a/model/picketlink/src/main/java/org/keycloak/models/picketlink/RealmAdapter.java b/model/picketlink/src/main/java/org/keycloak/models/picketlink/RealmAdapter.java
index d4c1624..36d7098 100755
--- a/model/picketlink/src/main/java/org/keycloak/models/picketlink/RealmAdapter.java
+++ b/model/picketlink/src/main/java/org/keycloak/models/picketlink/RealmAdapter.java
@@ -880,14 +880,14 @@ public class RealmAdapter implements RealmModel {
     public UserModel getUserBySocialLink(SocialLinkModel socialLink) {
         RelationshipQuery<SocialLinkRelationship> query = getRelationshipManager().createRelationshipQuery(SocialLinkRelationship.class);
         query.setParameter(SocialLinkRelationship.SOCIAL_PROVIDER, socialLink.getSocialProvider());
-        query.setParameter(SocialLinkRelationship.SOCIAL_USERNAME, socialLink.getSocialUsername());
+        query.setParameter(SocialLinkRelationship.SOCIAL_USERNAME, socialLink.getSocialUserId());
         query.setParameter(SocialLinkRelationship.REALM, realm.getName());
         List<SocialLinkRelationship> results = query.getResultList();
         if (results.isEmpty()) {
             return null;
         } else if (results.size() > 1) {
             throw new IllegalStateException("More results found for socialProvider=" + socialLink.getSocialProvider() +
-                    ", socialUsername=" + socialLink.getSocialUsername() + ", results=" + results);
+                    ", socialUserId=" + socialLink.getSocialUserId() + ", results=" + results);
         } else {
             User user = results.get(0).getUser();
             return new UserAdapter(user, getIdm());
@@ -902,7 +902,7 @@ public class RealmAdapter implements RealmModel {
 
         Set<SocialLinkModel> results = new HashSet<SocialLinkModel>();
         for (SocialLinkRelationship relationship : plSocialLinks) {
-            results.add(new SocialLinkModel(relationship.getSocialProvider(), relationship.getSocialUsername()));
+            results.add(new SocialLinkModel(relationship.getSocialProvider(), relationship.getSocialUserId()));
         }
         return results;
     }
@@ -912,7 +912,7 @@ public class RealmAdapter implements RealmModel {
         SocialLinkRelationship relationship = new SocialLinkRelationship();
         relationship.setUser(((UserAdapter)user).getUser());
         relationship.setSocialProvider(socialLink.getSocialProvider());
-        relationship.setSocialUsername(socialLink.getSocialUsername());
+        relationship.setSocialUserId(socialLink.getSocialUserId());
         relationship.setRealm(realm.getName());
 
         getRelationshipManager().add(relationship);
@@ -923,7 +923,7 @@ public class RealmAdapter implements RealmModel {
         SocialLinkRelationship relationship = new SocialLinkRelationship();
         relationship.setUser(((UserAdapter)user).getUser());
         relationship.setSocialProvider(socialLink.getSocialProvider());
-        relationship.setSocialUsername(socialLink.getSocialUsername());
+        relationship.setSocialUserId(socialLink.getSocialUserId());
         relationship.setRealm(realm.getName());
 
         getRelationshipManager().remove(relationship);
diff --git a/model/picketlink/src/main/java/org/keycloak/models/picketlink/relationships/SocialLinkRelationship.java b/model/picketlink/src/main/java/org/keycloak/models/picketlink/relationships/SocialLinkRelationship.java
index 62ec7e3..12f8d03 100755
--- a/model/picketlink/src/main/java/org/keycloak/models/picketlink/relationships/SocialLinkRelationship.java
+++ b/model/picketlink/src/main/java/org/keycloak/models/picketlink/relationships/SocialLinkRelationship.java
@@ -20,9 +20,9 @@ public class SocialLinkRelationship extends AbstractAttributedType implements Re
     private static final long serialVersionUID = 154879L;
 
     public static final AttributeParameter SOCIAL_PROVIDER = new AttributeParameter("socialProvider");
-    public static final AttributeParameter SOCIAL_USERNAME = new AttributeParameter("socialUsername");
+    public static final AttributeParameter SOCIAL_USERID = new AttributeParameter("socialUserId");
 
-    // realm is needed to allow searching as combination socialUsername+socialProvider may not be unique
+    // realm is needed to allow searching as combination socialUserId+socialProvider may not be unique
     // (Same user could have mapped same facebook account to username "foo" in "realm1" and to username "bar" in "realm2")
     public static final AttributeParameter REALM = new AttributeParameter("realm");
 
@@ -54,12 +54,12 @@ public class SocialLinkRelationship extends AbstractAttributedType implements Re
     }
 
     @AttributeProperty
-    public String getSocialUsername() {
-        return (String)getAttribute("socialUsername").getValue();
+    public String getSocialUserId() {
+        return (String)getAttribute("socialUserId").getValue();
     }
 
-    public void setSocialUsername(String socialProviderUserId) {
-        setAttribute(new Attribute<String>("socialUsername", socialProviderUserId));
+    public void setSocialUserId(String socialUserId) {
+        setAttribute(new Attribute<String>("socialUserId", socialUserId));
     }
 
     @AttributeProperty
diff --git a/model/tests/src/test/java/org/keycloak/model/test/AdapterTest.java b/model/tests/src/test/java/org/keycloak/model/test/AdapterTest.java
index 7a29945..9ea7b1e 100755
--- a/model/tests/src/test/java/org/keycloak/model/test/AdapterTest.java
+++ b/model/tests/src/test/java/org/keycloak/model/test/AdapterTest.java
@@ -156,7 +156,7 @@ public class AdapterTest extends AbstractModelTest {
         RoleModel appRole = app.addRole("test");
         realmModel.grantRole(user, appRole);
 
-        SocialLinkModel socialLink = new SocialLinkModel("google", user.getLoginName());
+        SocialLinkModel socialLink = new SocialLinkModel("google", "google1", user.getLoginName());
         realmModel.addSocialLink(user, socialLink);
 
         UserCredentialModel cred = new UserCredentialModel();
diff --git a/model/tests/src/test/java/org/keycloak/model/test/ImportTest.java b/model/tests/src/test/java/org/keycloak/model/test/ImportTest.java
index 3c41a5d..ae83661 100755
--- a/model/tests/src/test/java/org/keycloak/model/test/ImportTest.java
+++ b/model/tests/src/test/java/org/keycloak/model/test/ImportTest.java
@@ -133,22 +133,26 @@ public class ImportTest extends AbstractModelTest {
         for (SocialLinkModel socialLinkModel : socialLinks) {
             if ("facebook".equals(socialLinkModel.getSocialProvider())) {
                 facebookFound = true;
+                Assert.assertEquals(socialLinkModel.getSocialUserId(), "facebook1");
                 Assert.assertEquals(socialLinkModel.getSocialUsername(), "fbuser1");
             } else if ("google".equals(socialLinkModel.getSocialProvider())) {
                 googleFound = true;
+                Assert.assertEquals(socialLinkModel.getSocialUserId(), "google1");
                 Assert.assertEquals(socialLinkModel.getSocialUsername(), "mySocialUser@gmail.com");
             } else if ("twitter".equals(socialLinkModel.getSocialProvider())) {
                 twitterFound = true;
+                Assert.assertEquals(socialLinkModel.getSocialUserId(), "twitter1");
                 Assert.assertEquals(socialLinkModel.getSocialUsername(), "twuser1");
             }
         }
         Assert.assertTrue(facebookFound && twitterFound && googleFound);
 
-        UserModel foundSocialUser = realm.getUserBySocialLink(new SocialLinkModel("facebook", "fbuser1"));
+        UserModel foundSocialUser = realm.getUserBySocialLink(new SocialLinkModel("facebook", "facebook1", "fbuser1"));
         Assert.assertEquals(foundSocialUser.getLoginName(), socialUser.getLoginName());
-        Assert.assertNull(realm.getUserBySocialLink(new SocialLinkModel("facebook", "not-existing")));
+        Assert.assertNull(realm.getUserBySocialLink(new SocialLinkModel("facebook", "not-existing", "not-existing")));
 
         SocialLinkModel foundSocialLink = realm.getSocialLink(socialUser, "facebook");
+        Assert.assertEquals("facebook1", foundSocialLink.getSocialUserId());
         Assert.assertEquals("fbuser1", foundSocialLink.getSocialUsername());
         Assert.assertEquals("facebook", foundSocialLink.getSocialProvider());
 
diff --git a/model/tests/src/test/resources/testrealm.json b/model/tests/src/test/resources/testrealm.json
index 6f573d4..e0d6cfb 100755
--- a/model/tests/src/test/resources/testrealm.json
+++ b/model/tests/src/test/resources/testrealm.json
@@ -52,14 +52,17 @@
             "socialLinks": [
                 {
                     "socialProvider": "facebook",
+                    "socialUserId": "facebook1",
                     "socialUsername": "fbuser1"
                 },
                 {
                     "socialProvider": "twitter",
+                    "socialUserId": "twitter1",
                     "socialUsername": "twuser1"
                 },
                 {
                     "socialProvider": "google",
+                    "socialUserId": "google1",
                     "socialUsername": "mySocialUser@gmail.com"
                 }
             ]
diff --git a/services/src/main/java/org/keycloak/services/managers/RealmManager.java b/services/src/main/java/org/keycloak/services/managers/RealmManager.java
index dae9ae3..face034 100755
--- a/services/src/main/java/org/keycloak/services/managers/RealmManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/RealmManager.java
@@ -366,7 +366,7 @@ public class RealmManager {
             for (SocialMappingRepresentation socialMapping : rep.getSocialMappings()) {
                 UserModel user = userMap.get(socialMapping.getUsername());
                 for (SocialLinkRepresentation link : socialMapping.getSocialLinks()) {
-                    SocialLinkModel mappingModel = new SocialLinkModel(link.getSocialProvider(), link.getSocialUsername());
+                    SocialLinkModel mappingModel = new SocialLinkModel(link.getSocialProvider(), link.getSocialUserId(), link.getSocialUsername());
                     newRealm.addSocialLink(user, mappingModel);
                 }
             }
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 55cb9a3..d8ff9b1 100755
--- a/services/src/main/java/org/keycloak/services/resources/SocialResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/SocialResource.java
@@ -23,7 +23,6 @@ package org.keycloak.services.resources;
 
 import org.jboss.resteasy.logging.Logger;
 import org.jboss.resteasy.spi.HttpRequest;
-import org.jboss.resteasy.spi.HttpResponse;
 import org.keycloak.models.AccountRoles;
 import org.keycloak.models.ClientModel;
 import org.keycloak.models.Constants;
@@ -32,22 +31,20 @@ import org.keycloak.models.RealmModel;
 import org.keycloak.models.SocialLinkModel;
 import org.keycloak.models.UserModel;
 import org.keycloak.models.utils.KeycloakModelUtils;
-import org.keycloak.services.managers.AppAuthManager;
-import org.keycloak.services.managers.Auth;
 import org.keycloak.services.managers.AuthenticationManager;
 import org.keycloak.services.managers.RealmManager;
+import org.keycloak.services.managers.SocialRequestManager;
 import org.keycloak.services.managers.TokenManager;
 import org.keycloak.services.resources.flows.Flows;
 import org.keycloak.services.resources.flows.OAuthFlows;
 import org.keycloak.services.resources.flows.Urls;
 import org.keycloak.social.AuthCallback;
-import org.keycloak.social.AuthRequest;
 import org.keycloak.social.RequestDetails;
+import org.keycloak.social.SocialAccessDeniedException;
 import org.keycloak.social.SocialLoader;
 import org.keycloak.social.SocialProvider;
 import org.keycloak.social.SocialProviderConfig;
 import org.keycloak.social.SocialProviderException;
-import org.keycloak.services.managers.SocialRequestManager;
 import org.keycloak.social.SocialUser;
 
 import javax.ws.rs.GET;
@@ -57,6 +54,7 @@ import javax.ws.rs.QueryParam;
 import javax.ws.rs.container.ResourceContext;
 import javax.ws.rs.core.Context;
 import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.MultivaluedHashMap;
 import javax.ws.rs.core.Response;
 import javax.ws.rs.core.Response.Status;
 import javax.ws.rs.core.UriBuilder;
@@ -141,12 +139,20 @@ public class SocialResource {
         SocialUser socialUser;
         try {
             socialUser = provider.processCallback(config, callback);
+        } catch (SocialAccessDeniedException e) {
+            MultivaluedHashMap<String, String> queryParms = new MultivaluedHashMap<String, String>();
+            queryParms.putSingle("client_id", requestData.getClientAttribute("clientId"));
+            queryParms.putSingle("state", requestData.getClientAttribute("state"));
+            queryParms.putSingle("scope", requestData.getClientAttribute("scope"));
+            queryParms.putSingle("redirect_uri", requestData.getClientAttribute("redirectUri"));
+            queryParms.putSingle("response_type", requestData.getClientAttribute("responseType"));
+            return  Flows.forms(realm, request, uriInfo).setQueryParams(queryParms).setWarning("Access denied").createLogin();
         } catch (SocialProviderException e) {
             logger.warn("Failed to process social callback", e);
             return oauth.forwardToSecurityFailure("Failed to process social callback");
         }
 
-        SocialLinkModel socialLink = new SocialLinkModel(provider.getId(), socialUser.getId());
+        SocialLinkModel socialLink = new SocialLinkModel(provider.getId(), socialUser.getId(), socialUser.getUsername());
         UserModel user = realm.getUserBySocialLink(socialLink);
 
         // Check if user is already authenticated (this means linking social into existing user account)
@@ -210,7 +216,7 @@ public class SocialResource {
     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) {
+                                           @QueryParam("redirect_uri") String redirectUri, @QueryParam("response_type") String responseType) {
         RealmManager realmManager = new RealmManager(session);
         RealmModel realm = realmManager.getRealmByName(realmName);
 
@@ -239,20 +245,24 @@ public class SocialResource {
                     .putClientAttribute("realm", realmName)
                     .putClientAttribute("clientId", clientId).putClientAttribute("scope", scope)
                     .putClientAttribute("state", state).putClientAttribute("redirectUri", redirectUri)
-                    .redirectToSocialProvider();
+                    .putClientAttribute("responseType", responseType).redirectToSocialProvider();
         } catch (Throwable t) {
             return Flows.forms(realm, request, uriInfo).setError("Failed to redirect to social auth").createErrorPage();
         }
     }
 
     private RequestDetails getRequestDetails(Map<String, String[]> queryParams) {
-        for (SocialProvider provider : SocialLoader.load()) {
-            if (queryParams.containsKey(provider.getRequestIdParamName())) {
-                String requestId = queryParams.get(provider.getRequestIdParamName())[0];
-                if (socialRequestManager.isRequestId(requestId)) {
-                    return socialRequestManager.retrieveData(requestId);
-                }
-            }
+        String requestId = null;
+        if (queryParams.containsKey("state")) {
+            requestId =  queryParams.get("state")[0];
+        } else if (queryParams.containsKey("oauth_token")) {
+            requestId = queryParams.get("oauth_token")[0];
+        } else if (queryParams.containsKey("denied")) {
+            requestId = queryParams.get("denied")[0];
+        }
+
+        if (requestId != null && socialRequestManager.isRequestId(requestId)) {
+            return socialRequestManager.retrieveData(requestId);
         }
 
         return null;
diff --git a/social/core/src/main/java/org/keycloak/social/AbstractOAuth2Provider.java b/social/core/src/main/java/org/keycloak/social/AbstractOAuth2Provider.java
index ae8a0a5..7c6d2d7 100644
--- a/social/core/src/main/java/org/keycloak/social/AbstractOAuth2Provider.java
+++ b/social/core/src/main/java/org/keycloak/social/AbstractOAuth2Provider.java
@@ -51,6 +51,15 @@ public abstract class AbstractOAuth2Provider implements SocialProvider {
 
     @Override
     public SocialUser processCallback(SocialProviderConfig config, AuthCallback callback) throws SocialProviderException {
+        String error = callback.getQueryParam("error");
+        if (error != null) {
+            if (error.equals("access_denied")) {
+                throw new SocialAccessDeniedException();
+            } else {
+                throw new SocialProviderException(error);
+            }
+        }
+
         try {
             String code = callback.getQueryParam(CODE);
 
@@ -82,9 +91,4 @@ public abstract class AbstractOAuth2Provider implements SocialProvider {
         }
     }
 
-    @Override
-    public String getRequestIdParamName() {
-        return STATE;
-    }
-
 }
diff --git a/social/core/src/main/java/org/keycloak/social/AuthCallback.java b/social/core/src/main/java/org/keycloak/social/AuthCallback.java
index d9c579a..3ad0774 100644
--- a/social/core/src/main/java/org/keycloak/social/AuthCallback.java
+++ b/social/core/src/main/java/org/keycloak/social/AuthCallback.java
@@ -43,7 +43,7 @@ public class AuthCallback {
 
     public String getQueryParam(String name) {
         String[] value = queryParams.get(name);
-        if (value.length > 0) {
+        if (value != null && value.length > 0) {
             return value[0];
         }
         return null;
diff --git a/social/core/src/main/java/org/keycloak/social/SocialAccessDeniedException.java b/social/core/src/main/java/org/keycloak/social/SocialAccessDeniedException.java
new file mode 100644
index 0000000..6f9d0c7
--- /dev/null
+++ b/social/core/src/main/java/org/keycloak/social/SocialAccessDeniedException.java
@@ -0,0 +1,7 @@
+package org.keycloak.social;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class SocialAccessDeniedException extends SocialProviderException {
+}
diff --git a/social/core/src/main/java/org/keycloak/social/SocialProvider.java b/social/core/src/main/java/org/keycloak/social/SocialProvider.java
index 70da76d..73a8f21 100644
--- a/social/core/src/main/java/org/keycloak/social/SocialProvider.java
+++ b/social/core/src/main/java/org/keycloak/social/SocialProvider.java
@@ -31,8 +31,6 @@ public interface SocialProvider {
 
     AuthRequest getAuthUrl(SocialProviderConfig config) throws SocialProviderException;
 
-    String getRequestIdParamName();
-
     String getName();
 
     SocialUser processCallback(SocialProviderConfig config, AuthCallback callback) throws SocialProviderException;
diff --git a/social/core/src/main/java/org/keycloak/social/SocialProviderException.java b/social/core/src/main/java/org/keycloak/social/SocialProviderException.java
index 98cd2c3..5697046 100644
--- a/social/core/src/main/java/org/keycloak/social/SocialProviderException.java
+++ b/social/core/src/main/java/org/keycloak/social/SocialProviderException.java
@@ -28,6 +28,9 @@ public class SocialProviderException extends Exception {
 
     private static final long serialVersionUID = 1L;
 
+    protected SocialProviderException() {
+    }
+
     public SocialProviderException(String message) {
         super(message);
     }
diff --git a/social/core/src/main/java/org/keycloak/social/SocialUser.java b/social/core/src/main/java/org/keycloak/social/SocialUser.java
index 2c69456..9a1faed 100644
--- a/social/core/src/main/java/org/keycloak/social/SocialUser.java
+++ b/social/core/src/main/java/org/keycloak/social/SocialUser.java
@@ -3,12 +3,14 @@ package org.keycloak.social;
 public class SocialUser {
     
     private String id;
+    private String username;
     private String firstName;
     private String lastName;
     private String email;
 
-    public SocialUser(String id) {
+    public SocialUser(String id, String username) {
         this.id = id;
+        this.username = username;
     }
 
     public String getId() {
@@ -19,6 +21,14 @@ public class SocialUser {
         this.id = id;
     }
 
+    public String getUsername() {
+        return username;
+    }
+
+    public void setUsername(String username) {
+        this.username = username;
+    }
+
     public String getFirstName() {
         return firstName;
     }
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 cc62c52..9f64036 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
@@ -52,7 +52,7 @@ public class FacebookProvider extends AbstractOAuth2Provider {
         try {
             JSONObject profile = SimpleHttp.doGet(PROFILE_URL).header("Authorization", "Bearer " + accessToken).asJson();
 
-            SocialUser user = new SocialUser(profile.getString("id"));
+            SocialUser user = new SocialUser(profile.getString("id"), profile.getString("username"));
             user.setName(profile.optString("first_name"), profile.optString("last_name"));
             user.setEmail(profile.optString("email"));
 
@@ -64,10 +64,6 @@ public class FacebookProvider extends AbstractOAuth2Provider {
 
     @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 super.getAuthUrl(config);
     }
 
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
index ad38dcd..95c1067 100755
--- a/social/github/src/main/java/org/keycloak/social/github/GitHubProvider.java
+++ b/social/github/src/main/java/org/keycloak/social/github/GitHubProvider.java
@@ -52,7 +52,7 @@ public class GitHubProvider extends AbstractOAuth2Provider {
         try {
             JSONObject profile = SimpleHttp.doGet(PROFILE_URL).header("Authorization", "Bearer " + accessToken).asJson();
 
-            SocialUser user = new SocialUser(profile.get("id").toString());
+            SocialUser user = new SocialUser(profile.get("id").toString(), profile.getString("login"));
             user.setName(profile.optString("name"));
             user.setEmail(profile.optString("email"));
 
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 08b1104..ab3d8f4 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
@@ -71,7 +71,7 @@ public class GoogleProvider extends AbstractOAuth2Provider {
         try {
             JSONObject profile = SimpleHttp.doGet(PROFILE_URL).header("Authorization", "Bearer " + accessToken).asJson();
 
-            SocialUser user = new SocialUser(profile.getString("sub"));
+            SocialUser user = new SocialUser(profile.getString("sub"), profile.getString("email"));
             user.setName(profile.optString("given_name"), profile.optString("family_name"));
             user.setEmail(profile.optString("email"));
 
diff --git a/social/twitter/src/main/java/org/keycloak/social/twitter/TwitterProvider.java b/social/twitter/src/main/java/org/keycloak/social/twitter/TwitterProvider.java
index fda4c6a..10df5cd 100755
--- a/social/twitter/src/main/java/org/keycloak/social/twitter/TwitterProvider.java
+++ b/social/twitter/src/main/java/org/keycloak/social/twitter/TwitterProvider.java
@@ -23,6 +23,7 @@ package org.keycloak.social.twitter;
 
 import org.keycloak.social.AuthCallback;
 import org.keycloak.social.AuthRequest;
+import org.keycloak.social.SocialAccessDeniedException;
 import org.keycloak.social.SocialProvider;
 import org.keycloak.social.SocialProviderConfig;
 import org.keycloak.social.SocialProviderException;
@@ -67,6 +68,10 @@ public class TwitterProvider implements SocialProvider {
 
     @Override
     public SocialUser processCallback(SocialProviderConfig config, AuthCallback callback) throws SocialProviderException {
+        if (callback.getQueryParam("denied") != null) {
+            throw new SocialAccessDeniedException();
+        }
+
         try {
             Twitter twitter = new TwitterFactory().getInstance();
             twitter.setOAuthConsumer(config.getKey(), config.getSecret());
@@ -77,7 +82,7 @@ public class TwitterProvider implements SocialProvider {
             twitter.getOAuthAccessToken(requestToken, verifier);
             twitter4j.User twitterUser = twitter.verifyCredentials();
 
-            SocialUser user = new SocialUser(Long.toString(twitterUser.getId()));
+            SocialUser user = new SocialUser(Long.toString(twitterUser.getId()), twitterUser.getScreenName());
             user.setName(twitterUser.getName());
 
             return user;
@@ -86,9 +91,4 @@ public class TwitterProvider implements SocialProvider {
         }
     }
 
-    @Override
-    public String getRequestIdParamName() {
-        return "oauth_token";
-    }
-
 }
diff --git a/testsuite/integration/src/main/java/org/keycloak/testutils/KeycloakServer.java b/testsuite/integration/src/main/java/org/keycloak/testutils/KeycloakServer.java
index 68d6c5e..0a0e463 100755
--- a/testsuite/integration/src/main/java/org/keycloak/testutils/KeycloakServer.java
+++ b/testsuite/integration/src/main/java/org/keycloak/testutils/KeycloakServer.java
@@ -202,11 +202,12 @@ public class KeycloakServer {
         try {
             RealmManager manager = new RealmManager(session);
 
-            if (rep.getId() == null) {
-                throw new RuntimeException("Realm id not specified");
+            if (rep.getId() != null && manager.getRealm(rep.getId()) != null) {
+                info("Not importing realm " + rep.getRealm() + " realm already exists");
+                return;
             }
 
-            if (manager.getRealm(rep.getId()) != null) {
+            if (manager.getRealmByName(rep.getRealm()) != null) {
                 info("Not importing realm " + rep.getRealm() + " realm already exists");
                 return;
             }
@@ -268,7 +269,7 @@ public class KeycloakServer {
 
         server.deploy(di);
 
-        factory = ((KeycloakApplication)deployment.getApplication()).getFactory();
+        factory = ((KeycloakApplication) deployment.getApplication()).getFactory();
 
         setupDevConfig();
 
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/DummySocial.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/DummySocial.java
index ee4607e..5ad7cf6 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/DummySocial.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/DummySocial.java
@@ -2,6 +2,7 @@ package org.keycloak.testsuite;
 
 import org.keycloak.social.AuthCallback;
 import org.keycloak.social.AuthRequest;
+import org.keycloak.social.SocialAccessDeniedException;
 import org.keycloak.social.SocialProvider;
 import org.keycloak.social.SocialProviderConfig;
 import org.keycloak.social.SocialProviderException;
@@ -27,23 +28,24 @@ public class DummySocial implements SocialProvider {
     }
 
     @Override
-    public String getRequestIdParamName() {
-        return "state";
-    }
-
-    @Override
     public String getName() {
         return "Dummy Provider";
     }
 
     @Override
     public SocialUser processCallback(SocialProviderConfig config, AuthCallback callback) throws SocialProviderException {
+        String error = callback.getQueryParam("error");
+        if (error != null) {
+            throw new SocialAccessDeniedException();
+        }
+
         if (!callback.getQueryParam("state").equals(callback.getAttribute("state"))) {
             throw new SocialProviderException("Invalid state");
         }
 
+        String id = callback.getQueryParam("id");
         String username = callback.getQueryParam("username");
-        SocialUser user = new SocialUser(username);
+        SocialUser user = new SocialUser(id, username);
         user.setName(callback.getQueryParam("firstname"), callback.getQueryParam("lastname"));
         user.setEmail(callback.getQueryParam("email"));
         return user;
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/DummySocialServlet.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/DummySocialServlet.java
index 9278d36..e87ce1d 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/DummySocialServlet.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/DummySocialServlet.java
@@ -22,11 +22,13 @@ public class DummySocialServlet extends HttpServlet {
         pw.print("<html>");
         pw.print("<body>");
         pw.print("<form method=\"post\">");
+        pw.print("<label for=\"id\">ID</label><input type=\"text\" id=\"id\" name=\"id\" />");
         pw.print("<label for=\"username\">Username</label><input type=\"text\" id=\"username\" name=\"username\" />");
         pw.print("<label for=\"firstname\">First Name</label><input type=\"text\" id=\"firstname\" name=\"firstname\" />");
         pw.print("<label for=\"lastname\">Last Name</label><input type=\"text\" id=\"lastname\" name=\"lastname\" />");
         pw.print("<label for=\"email\">Email</label><input type=\"text\" id=\"email\" name=\"email\" />");
-        pw.print("<input type=\"submit\" id=\"submit\" value=\"login\" />");
+        pw.print("<input type=\"submit\" id=\"login\" name=\"login\" value=\"login\" />");
+        pw.print("<input type=\"submit\" id=\"cancel\" name=\"cancel\" value=\"cancel\" />");
         pw.print("</form>");
         pw.print("</body>");
         pw.print("</html>");
@@ -53,15 +55,20 @@ public class DummySocialServlet extends HttpServlet {
             }
         }
 
-        String redirect = redirectUri + "?username=" + req.getParameter("username") + "&state=" + state + "&code=" + UUID.randomUUID().toString();
-        if (req.getParameter("firstname") != null) {
-            redirect += "&firstname=" + req.getParameter("firstname");
-        }
-        if (req.getParameter("lastname") != null) {
-            redirect += "&lastname=" + req.getParameter("lastname");
-        }
-        if (req.getParameter("email") != null) {
-            redirect += "&email=" + req.getParameter("email");
+        String redirect;
+        if (req.getParameter("login") != null) {
+            redirect = redirectUri + "?id=" + req.getParameter("id") + "&username=" + req.getParameter("username") + "&state=" + state + "&code=" + UUID.randomUUID().toString();
+            if (req.getParameter("firstname") != null) {
+                redirect += "&firstname=" + req.getParameter("firstname");
+            }
+            if (req.getParameter("lastname") != null) {
+                redirect += "&lastname=" + req.getParameter("lastname");
+            }
+            if (req.getParameter("email") != null) {
+                redirect += "&email=" + req.getParameter("email");
+            }
+        } else {
+            redirect = redirectUri + "?error=access_denied&state=" + state;
         }
 
         resp.sendRedirect(redirect);
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginPage.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginPage.java
index 3671ae0..a266b8a 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginPage.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginPage.java
@@ -62,6 +62,9 @@ public class LoginPage extends AbstractPage {
     @FindBy(className = "feedback-error")
     private WebElement loginErrorMessage;
 
+    @FindBy(className = "feedback-warning")
+    private WebElement loginWarningMessage;
+
     public void login(String username, String password) {
         usernameInput.clear();
         usernameInput.sendKeys(username);
@@ -80,6 +83,11 @@ public class LoginPage extends AbstractPage {
         return loginErrorMessage != null ? loginErrorMessage.getText() : null;
     }
 
+    public String getWarning() {
+        return loginWarningMessage != null ? loginWarningMessage.getText() : null;
+    }
+
+
     public boolean isCurrent() {
         return driver.getTitle().equals("Log in to test");
     }
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/social/SocialLoginTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/social/SocialLoginTest.java
index 27e7a87..18c4281 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/social/SocialLoginTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/social/SocialLoginTest.java
@@ -97,11 +97,12 @@ public class SocialLoginTest {
 
         loginPage.clickSocial("dummy");
 
+        driver.findElement(By.id("id")).sendKeys("1");
         driver.findElement(By.id("username")).sendKeys("dummy-user1");
         driver.findElement(By.id("firstname")).sendKeys("Bob");
         driver.findElement(By.id("lastname")).sendKeys("Builder");
         driver.findElement(By.id("email")).sendKeys("bob@builder.com");
-        driver.findElement(By.id("submit")).click();
+        driver.findElement(By.id("login")).click();
 
         Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
 
@@ -118,6 +119,23 @@ public class SocialLoginTest {
         Assert.assertEquals("bob@builder.com", profile.getEmail());
     }
 
+
+    @Test
+    public void loginCancelled() throws Exception {
+        loginPage.open();
+
+        loginPage.clickSocial("dummy");
+
+        driver.findElement(By.id("cancel")).click();
+
+        Assert.assertTrue(loginPage.isCurrent());
+        Assert.assertEquals("Access denied", loginPage.getWarning());
+
+        loginPage.login("test-user@localhost", "password");
+
+        Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
+    }
+
     @Test
     public void profileUpdateRequired() {
         keycloakRule.configure(new KeycloakSetup() {
@@ -132,11 +150,12 @@ public class SocialLoginTest {
 
             loginPage.clickSocial("dummy");
 
+            driver.findElement(By.id("id")).sendKeys("2");
             driver.findElement(By.id("username")).sendKeys("dummy-user2");
             driver.findElement(By.id("firstname")).sendKeys("Bob");
             driver.findElement(By.id("lastname")).sendKeys("Builder");
             driver.findElement(By.id("email")).sendKeys("bob@builder.com");
-            driver.findElement(By.id("submit")).click();
+            driver.findElement(By.id("login")).click();
 
             profilePage.isCurrent();
 
diff --git a/testsuite/performance/src/test/java/org/keycloak/testsuite/performance/CreateUsersWorker.java b/testsuite/performance/src/test/java/org/keycloak/testsuite/performance/CreateUsersWorker.java
index 12fced6..62d22af 100755
--- a/testsuite/performance/src/test/java/org/keycloak/testsuite/performance/CreateUsersWorker.java
+++ b/testsuite/performance/src/test/java/org/keycloak/testsuite/performance/CreateUsersWorker.java
@@ -97,7 +97,7 @@ public class CreateUsersWorker implements Worker {
                         + " which is too big.");
             }
 
-            SocialLinkModel socialLink = new SocialLinkModel(socialProvider, username);
+            SocialLinkModel socialLink = new SocialLinkModel(socialProvider, username, username);
             realm.addSocialLink(user, socialLink);
         }
 
diff --git a/testsuite/performance/src/test/java/org/keycloak/testsuite/performance/ReadUsersWorker.java b/testsuite/performance/src/test/java/org/keycloak/testsuite/performance/ReadUsersWorker.java
index 80c8b57..c125ac5 100755
--- a/testsuite/performance/src/test/java/org/keycloak/testsuite/performance/ReadUsersWorker.java
+++ b/testsuite/performance/src/test/java/org/keycloak/testsuite/performance/ReadUsersWorker.java
@@ -112,7 +112,7 @@ public class ReadUsersWorker implements Worker {
 
             // Try to search by social links
             if (searchBySocialLinks) {
-                SocialLinkModel socialLink = new SocialLinkModel("facebook", username);
+                SocialLinkModel socialLink = new SocialLinkModel("facebook", username, username);
                 realm.getUserBySocialLink(socialLink);
             }
         }