keycloak-memoizeit

KEYCLOAK-25 Added flow with user registration with prefilled

8/20/2013 7:42:19 AM

Details

diff --git a/examples/as7-eap-demo/server/src/main/resources/META-INF/testrealm.json b/examples/as7-eap-demo/server/src/main/resources/META-INF/testrealm.json
index f69d0bd..bb918ef 100755
--- a/examples/as7-eap-demo/server/src/main/resources/META-INF/testrealm.json
+++ b/examples/as7-eap-demo/server/src/main/resources/META-INF/testrealm.json
@@ -6,7 +6,8 @@
     "sslNotRequired": true,
     "cookieLoginAllowed": true,
     "registrationAllowed": true,
-    "automaticRegistrationAfterSocialLogin": true,
+    "social": true,
+    "automaticRegistrationAfterSocialLogin": false,
     "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" ],
diff --git a/forms/src/main/java/org/keycloak/forms/RegisterBean.java b/forms/src/main/java/org/keycloak/forms/RegisterBean.java
index 60d247a..77d6a8c 100755
--- a/forms/src/main/java/org/keycloak/forms/RegisterBean.java
+++ b/forms/src/main/java/org/keycloak/forms/RegisterBean.java
@@ -26,6 +26,7 @@ import java.util.Map;
 
 import javax.annotation.PostConstruct;
 import javax.faces.bean.ManagedBean;
+import javax.faces.bean.ManagedProperty;
 import javax.faces.bean.RequestScoped;
 import javax.faces.context.FacesContext;
 import javax.servlet.http.HttpServletRequest;
@@ -42,6 +43,8 @@ public class RegisterBean {
 
     private HashMap<String, String> formData;
 
+    private boolean socialRegistration;
+
     @PostConstruct
     public void init() {
         FacesContext ctx = FacesContext.getCurrentInstance();
@@ -49,6 +52,9 @@ public class RegisterBean {
 
         this.formData = new HashMap<String, String>();
 
+        Boolean socialRegistrationAttr = (Boolean)request.getAttribute(FormFlows.SOCIAL_REGISTRATION);
+        this.socialRegistration = socialRegistrationAttr != null && socialRegistrationAttr;
+
         @SuppressWarnings("unchecked")
         MultivaluedMap<String, String> formData = (MultivaluedMap<String, String>) request.getAttribute(FormFlows.DATA);
         if (formData != null) {
@@ -62,4 +68,8 @@ public class RegisterBean {
         return formData;
     }
 
+    public boolean isSocialRegistration() {
+        return socialRegistration;
+    }
+
 }
diff --git a/forms/src/main/java/org/keycloak/forms/SocialBean.java b/forms/src/main/java/org/keycloak/forms/SocialBean.java
index b007083..fab91b1 100644
--- a/forms/src/main/java/org/keycloak/forms/SocialBean.java
+++ b/forms/src/main/java/org/keycloak/forms/SocialBean.java
@@ -46,6 +46,9 @@ public class SocialBean {
     @ManagedProperty(value = "#{realm}")
     private RealmBean realm;
 
+    @ManagedProperty(value = "#{register}")
+    private RegisterBean registerBean;
+
     @ManagedProperty(value = "#{url}")
     private UrlBean url;
 
@@ -73,4 +76,32 @@ public class SocialBean {
         return providers;
     }
 
+    // Display panel with social providers just in case that social is enabled for realm, but we are not in the middle of registration with social
+    public boolean isDisplaySocialProviders() {
+        return realm.isSocial() && !registerBean.isSocialRegistration();
+    }
+
+    public RealmBean getRealm() {
+        return realm;
+    }
+
+    public void setRealm(RealmBean realm) {
+        this.realm = realm;
+    }
+
+    public UrlBean getUrl() {
+        return url;
+    }
+
+    public void setUrl(UrlBean url) {
+        this.url = url;
+    }
+
+    public RegisterBean getRegisterBean() {
+        return registerBean;
+    }
+
+    public void setRegisterBean(RegisterBean registerBean) {
+        this.registerBean = registerBean;
+    }
 }
diff --git a/forms/src/main/java/org/keycloak/forms/UrlBean.java b/forms/src/main/java/org/keycloak/forms/UrlBean.java
index ba00609..62017d0 100644
--- a/forms/src/main/java/org/keycloak/forms/UrlBean.java
+++ b/forms/src/main/java/org/keycloak/forms/UrlBean.java
@@ -45,6 +45,9 @@ public class UrlBean {
     @ManagedProperty(value = "#{realm}")
     private RealmBean realm;
 
+    @ManagedProperty(value = "#{register}")
+    private RegisterBean registerBean;
+
     @PostConstruct
     public void init() {
         FacesContext ctx = FacesContext.getCurrentInstance();
@@ -64,6 +67,14 @@ public class UrlBean {
         this.realm = realm;
     }
 
+    public RegisterBean getRegisterBean() {
+        return registerBean;
+    }
+
+    public void setRegisterBean(RegisterBean registerBean) {
+        this.registerBean = registerBean;
+    }
+
     public String getAccessUrl() {
         return Urls.accountAccessPage(baseURI, realm.getId()).toString();
     }
@@ -98,7 +109,10 @@ public class UrlBean {
 
     public String getRegistrationAction() {
         if (realm.isSaas()) {
+            // TODO: saas social registration
             return Urls.saasRegisterAction(baseURI).toString();
+        } else if (registerBean.isSocialRegistration()) {
+            return Urls.socialRegisterAction(baseURI, realm.getId()).toString();
         } else {
             return Urls.realmRegisterAction(baseURI, realm.getId()).toString();
         }
diff --git a/forms/src/main/resources/META-INF/resources/forms/theme/default/register.xhtml b/forms/src/main/resources/META-INF/resources/forms/theme/default/register.xhtml
index 0078271..ca9d8b6 100755
--- a/forms/src/main/resources/META-INF/resources/forms/theme/default/register.xhtml
+++ b/forms/src/main/resources/META-INF/resources/forms/theme/default/register.xhtml
@@ -39,6 +39,8 @@
 	</ui:define>
 	
 	<ui:define name="info">
-		<p>#{messages.alreadyHaveAccount} <a href="#{url.loginUrl}">#{messages.logIn}</a>.</p>
+        <h:panelGroup rendered="#{not register.socialRegistration}">
+            <p>#{messages.alreadyHaveAccount} <a href="#{url.loginUrl}">#{messages.logIn}</a>.</p>
+        </h:panelGroup>
 	</ui:define>
 </ui:composition>
\ No newline at end of file
diff --git a/forms/src/main/resources/META-INF/resources/forms/theme/default/template-login.xhtml b/forms/src/main/resources/META-INF/resources/forms/theme/default/template-login.xhtml
index 05ee4fb..e99a442 100644
--- a/forms/src/main/resources/META-INF/resources/forms/theme/default/template-login.xhtml
+++ b/forms/src/main/resources/META-INF/resources/forms/theme/default/template-login.xhtml
@@ -39,12 +39,12 @@ body {
 						</div>
 				</h:panelGroup>
 
-				<h:panelGroup rendered="#{realm.social}">
+				<h:panelGroup rendered="#{social.displaySocialProviders}">
 					<section class="social-login"> <span>or</span>
 					<h3>Social login area</h3>
 					<p>#{messages.logInWith}</p>
 					<ul>
-						<ui:repeat var="p" value="#{forms.providers}">
+						<ui:repeat var="p" value="#{social.providers}">
 							<li><a href="#{p.loginUrl}" class="zocial #{p.id}"> <span class="text">#{p.name}</span></a></li>
 						</ui:repeat>
 					</ul>
diff --git a/services/src/main/java/org/keycloak/services/resources/flows/FormFlows.java b/services/src/main/java/org/keycloak/services/resources/flows/FormFlows.java
index 3804b44..d1e3407 100644
--- a/services/src/main/java/org/keycloak/services/resources/flows/FormFlows.java
+++ b/services/src/main/java/org/keycloak/services/resources/flows/FormFlows.java
@@ -38,6 +38,7 @@ public class FormFlows {
     public static final String ERROR_MESSAGE = "KEYCLOAK_FORMS_ERROR_MESSAGE";
     public static final String REALM = Realm.class.getName();
     public static final String USER = UserModel.class.getName();
+    public static final String SOCIAL_REGISTRATION = "socialRegistration";
 
     private String error;
     private MultivaluedMap<String, String> formData;
@@ -47,6 +48,8 @@ public class FormFlows {
     private HttpRequest request;
     private UserModel userModel;
 
+    private boolean socialRegistration;
+
     FormFlows(RealmModel realm, HttpRequest request) {
         this.realm = realm;
         this.request = request;
@@ -75,6 +78,8 @@ public class FormFlows {
             request.setAttribute(USER, userModel);
         }
 
+        request.setAttribute(SOCIAL_REGISTRATION, socialRegistration);
+
         request.forward(form);
         return null;
     }
@@ -113,6 +118,12 @@ public class FormFlows {
         return this;
     }
 
+    // Set flag whether user registration is triggered from social login
+    public FormFlows setSocialRegistration(boolean socialRegistration) {
+        this.socialRegistration = socialRegistration;
+        return this;
+    }
+
     public FormFlows setFormData(MultivaluedMap<String, String> formData) {
         this.formData = formData;
         return this;
diff --git a/services/src/main/java/org/keycloak/services/resources/flows/Urls.java b/services/src/main/java/org/keycloak/services/resources/flows/Urls.java
index 57fc5fb..3079602 100644
--- a/services/src/main/java/org/keycloak/services/resources/flows/Urls.java
+++ b/services/src/main/java/org/keycloak/services/resources/flows/Urls.java
@@ -100,7 +100,7 @@ public class Urls {
         return saasBase(baseUri).path(SaasService.class, "registerPage").build();
     }
 
-    private static UriBuilder socialBase(URI baseUri) {
+    public static UriBuilder socialBase(URI baseUri) {
         return UriBuilder.fromUri(baseUri).path(SocialResource.class);
     }
 
@@ -116,4 +116,8 @@ public class Urls {
     private static UriBuilder tokenBase(URI baseUri) {
         return realmBase(baseUri).path(RealmsResource.class, "getTokenService");
     }
+
+    public static URI socialRegisterAction(URI baseUri, String realmId) {
+        return socialBase(baseUri).path(SocialResource.class, "socialRegistration").build(realmId);
+    }
 }
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 c5596f4..0f64e5d 100644
--- a/services/src/main/java/org/keycloak/services/resources/SocialResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/SocialResource.java
@@ -27,34 +27,48 @@ import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
+import java.util.UUID;
 
 import javax.imageio.spi.ServiceRegistry;
+import javax.ws.rs.Consumes;
 import javax.ws.rs.GET;
+import javax.ws.rs.NotFoundException;
+import javax.ws.rs.POST;
 import javax.ws.rs.Path;
 import javax.ws.rs.PathParam;
 import javax.ws.rs.QueryParam;
+import javax.ws.rs.container.ResourceContext;
 import javax.ws.rs.core.Context;
+import javax.ws.rs.core.Cookie;
 import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.core.NewCookie;
 import javax.ws.rs.core.Response;
 import javax.ws.rs.core.Response.Status;
 import javax.ws.rs.core.UriInfo;
 
 import org.jboss.resteasy.logging.Logger;
+import org.jboss.resteasy.specimpl.MultivaluedMapImpl;
 import org.jboss.resteasy.spi.HttpRequest;
+import org.jboss.resteasy.spi.HttpResponse;
 import org.keycloak.services.managers.AuthenticationManager;
 import org.keycloak.services.managers.RealmManager;
 import org.keycloak.services.managers.TokenManager;
+import org.keycloak.services.messages.Messages;
 import org.keycloak.services.models.RealmModel;
 import org.keycloak.services.models.RoleModel;
 import org.keycloak.services.models.SocialLinkModel;
 import org.keycloak.services.models.UserModel;
 import org.keycloak.services.resources.flows.Flows;
 import org.keycloak.services.resources.flows.OAuthFlows;
+import org.keycloak.services.resources.flows.PageFlows;
 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.RequestDetailsBuilder;
+import org.keycloak.social.SocialConstants;
 import org.keycloak.social.SocialProvider;
 import org.keycloak.social.SocialProviderConfig;
 import org.keycloak.social.SocialProviderException;
@@ -78,6 +92,12 @@ public class SocialResource {
     @Context
     private HttpRequest request;
 
+    @Context
+    private HttpResponse response;
+
+    @Context
+    ResourceContext resourceContext;
+
     private SocialRequestManager socialRequestManager;
 
     private TokenManager tokenManager;
@@ -147,7 +167,7 @@ public class SocialResource {
                     if (realm.isAutomaticRegistrationAfterSocialLogin()) {
 
                         if (realm.getUser(socialUser.getUsername()) != null) {
-                            // TODO: Username is already in realm. Show message and let user to bind accounts
+                            // TODO: Username is already in realm. Show message and let user to bind accounts after he re-authenticate
                             throw new IllegalStateException("Username " + socialUser.getUsername() +
                                     " already registered in the realm. TODO: bind accounts...");
 
@@ -155,6 +175,9 @@ public class SocialResource {
                             // already registered. But actually Keycloak allows duplicate emails
                         } else {
                             user = realm.addUser(socialUser.getUsername());
+                            user.setFirstName(socialUser.getFirstName());
+                            user.setLastName(socialUser.getLastName());
+                            user.setEmail(socialUser.getEmail());
                         }
 
                         realm.addSocialLink(user, socialLink);
@@ -163,7 +186,23 @@ public class SocialResource {
                             realm.grantRole(user, role);
                         }
                     }  else {
-                        // TODO: redirect to registration screen with pre-filled info
+                        // Redirect user to registration screen with prefilled data from social provider
+                        MultivaluedMap<String, String> formData = fillRegistrationFormWithSocialData(socialUser);
+
+                        RequestDetailsBuilder reqDetailsBuilder = RequestDetailsBuilder.createFromRequestDetails(requestData);
+                        reqDetailsBuilder.putSocialAttribute(SocialConstants.ATTR_SOCIAL_LINK, socialLink);
+
+                        String requestId = UUID.randomUUID().toString();
+                        socialRequestManager.addRequest(requestId, reqDetailsBuilder.build());
+                        boolean secureOnly = !realm.isSslNotRequired();
+                        String cookiePath = Urls.socialBase(uriInfo.getBaseUri()).build().getPath();
+                        logger.info("creating cookie for social registration - name: " + SocialConstants.SOCIAL_REGISTRATION_COOKIE
+                                + " path: " + cookiePath);
+                        NewCookie newCookie = new NewCookie(SocialConstants.SOCIAL_REGISTRATION_COOKIE, requestId,
+                                cookiePath, null, "Added social cookie", NewCookie.DEFAULT_MAX_AGE, secureOnly);
+                        response.addNewCookie(newCookie);
+
+                        return Flows.forms(realm, request).setFormData(formData).setSocialRegistration(true).forwardToRegistration();
                     }
                 }
 
@@ -213,6 +252,69 @@ public class SocialResource {
         }
     }
 
+    @POST
+    @Path("{realm}/socialRegistration")
+    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
+    public Response socialRegistration(@PathParam("realm") final String realmId,
+                                       final MultivaluedMap<String, String> formData) {
+        return new Transaction<Response>() {
+            protected Response callImpl() {
+                PageFlows pageFlows = Flows.pages(request);
+                Cookie cookie = headers.getCookies().get(SocialConstants.SOCIAL_REGISTRATION_COOKIE);
+                if (cookie == null) {
+                    return pageFlows.forwardToSecurityFailure("Social registration cookie not found");
+                }
+
+                String requestId = cookie.getValue();
+                if (!socialRequestManager.isRequestId(requestId)) {
+                    logger.error("Unknown requestId found in cookie. Maybe it's expired. requestId=" + requestId);
+                    return pageFlows.forwardToSecurityFailure("Unknown requestId found in cookie. Maybe it's expired.");
+                }
+
+                RequestDetails requestData = socialRequestManager.getData(requestId);
+
+                RealmManager realmManager = new RealmManager(session);
+                RealmModel realm = realmManager.getRealm(realmId);
+                if (realm == null || !realm.isEnabled()) {
+                    return pageFlows.forwardToSecurityFailure("Realm doesn't exists or is not enabled.");
+                }
+                TokenService tokenService = new TokenService(realm, tokenManager);
+                resourceContext.initResource(tokenService);
+
+                String clientId = requestData.getClientAttribute("clientId");
+                String scope = requestData.getClientAttribute("scope");
+                String state = requestData.getClientAttribute("state");
+                String redirectUri = requestData.getClientAttribute("redirectUri");
+                SocialLinkModel socialLink = (SocialLinkModel)requestData.getSocialAttribute(SocialConstants.ATTR_SOCIAL_LINK);
+
+                Response response1 = tokenService.processRegisterImpl(clientId, scope, state, redirectUri, formData, true);
+
+                // Some error occured during registration
+                if (response1 == null) {
+                    return null;
+                }
+
+                String username = formData.getFirst("username");
+                UserModel user = realm.getUser(username);
+                if (user == null) {
+                    // Normally shouldn't happen
+                    throw new IllegalStateException("User " + username + " not found in the realm");
+                }
+                realm.addSocialLink(user, socialLink);
+
+                // Expire cookie and invalidate requestData
+                String cookiePath = Urls.socialBase(uriInfo.getBaseUri()).build().getPath();
+                NewCookie newCookie = new NewCookie(SocialConstants.SOCIAL_REGISTRATION_COOKIE, "", cookiePath, null,
+                        "Expire social cookie", 0, false);
+                logger.info("Expiring social registration cookie: " + SocialConstants.SOCIAL_REGISTRATION_COOKIE + ", path: " + cookiePath);
+                response.addNewCookie(newCookie);
+                socialRequestManager.retrieveData(requestId);
+
+                return response1;
+            }
+        }.call();
+    }
+
     private RequestDetails getRequestDetails(Map<String, String[]> queryParams) {
         Iterator<SocialProvider> itr = ServiceRegistry.lookupProviders(SocialProvider.class);
 
@@ -251,4 +353,25 @@ public class SocialResource {
         return queryParams;
     }
 
+    protected MultivaluedMap<String, String> fillRegistrationFormWithSocialData(SocialUser socialUser) {
+        MultivaluedMap<String, String> formData = new MultivaluedMapImpl<String, String>();
+        formData.putSingle("username", socialUser.getUsername());
+
+        if (socialUser.getEmail() != null) {
+            formData.putSingle("email", socialUser.getEmail());
+        }
+
+        String fullName = null;
+        if (socialUser.getFirstName() == null) {
+            fullName = socialUser.getLastName();
+        } else if (socialUser.getLastName() == null) {
+            fullName = socialUser.getFirstName();
+        } else {
+            fullName = socialUser.getFirstName() + " " + socialUser.getLastName();
+        }
+
+        formData.putSingle("name", fullName);
+        return formData;
+    }
+
 }
diff --git a/services/src/main/java/org/keycloak/services/resources/TokenService.java b/services/src/main/java/org/keycloak/services/resources/TokenService.java
index cb63744..b05e498 100755
--- a/services/src/main/java/org/keycloak/services/resources/TokenService.java
+++ b/services/src/main/java/org/keycloak/services/resources/TokenService.java
@@ -249,84 +249,90 @@ public class TokenService {
         return new Transaction<Response>() {
             @Override
             protected Response callImpl() {
-                OAuthFlows oauth = Flows.oauth(realm, request, uriInfo, authManager, tokenManager);
+                return processRegisterImpl(clientId, scopeParam, state, redirect, formData, false);
+            }
+        }.call();
+    }
 
-                if (!realm.isEnabled()) {
-                    return oauth.forwardToSecurityFailure("Realm not enabled");
-                }
-                UserModel client = realm.getUser(clientId);
-                if (client == null) {
-                    return oauth.forwardToSecurityFailure("Unknown login requester.");
-                }
+    public Response processRegisterImpl(String clientId, String scopeParam, String state, String redirect,
+                                        MultivaluedMap<String, String> formData, boolean isSocialRegistration) {
+        OAuthFlows oauth = Flows.oauth(realm, request, uriInfo, authManager, tokenManager);
 
-                if (!client.isEnabled()) {
-                    return oauth.forwardToSecurityFailure("Login requester not enabled.");
-                }
+        if (!realm.isEnabled()) {
+            return oauth.forwardToSecurityFailure("Realm not enabled");
+        }
+        UserModel client = realm.getUser(clientId);
+        if (client == null) {
+            return oauth.forwardToSecurityFailure("Unknown login requester.");
+        }
 
-                if (!realm.isRegistrationAllowed()) {
-                    return oauth.forwardToSecurityFailure("Registration not allowed");
-                }
+        if (!client.isEnabled()) {
+            return oauth.forwardToSecurityFailure("Login requester not enabled.");
+        }
 
-                List<String> requiredCredentialTypes = new LinkedList<String>();
-                for (RequiredCredentialModel m : realm.getRequiredCredentials()) {
-                    requiredCredentialTypes.add(m.getType());
-                }
+        if (!realm.isRegistrationAllowed()) {
+            return oauth.forwardToSecurityFailure("Registration not allowed");
+        }
 
-                String error = Validation.validateRegistrationForm(formData, requiredCredentialTypes);
-                if (error != null) {
-                    return Flows.forms(realm, request).setError(error).setFormData(formData).forwardToRegistration();
-                }
+        List<String> requiredCredentialTypes = new LinkedList<String>();
+        for (RequiredCredentialModel m : realm.getRequiredCredentials()) {
+            requiredCredentialTypes.add(m.getType());
+        }
 
-                String username = formData.getFirst("username");
+        String error = Validation.validateRegistrationForm(formData, requiredCredentialTypes);
+        if (error != null) {
+            return Flows.forms(realm, request).setError(error).setFormData(formData)
+                    .setSocialRegistration(isSocialRegistration).forwardToRegistration();
+        }
 
-                UserModel user = realm.getUser(username);
-                if (user != null) {
-                    return Flows.forms(realm, request).setError(Messages.USERNAME_EXISTS).setFormData(formData)
-                            .forwardToRegistration();
-                }
-
-                user = realm.addUser(username);
-
-                String fullname = formData.getFirst("name");
-                if (fullname != null) {
-                    StringTokenizer tokenizer = new StringTokenizer(fullname, " ");
-                    StringBuffer first = null;
-                    String last = "";
-                    while (tokenizer.hasMoreTokens()) {
-                        String token = tokenizer.nextToken();
-                        if (tokenizer.hasMoreTokens()) {
-                            if (first == null) {
-                                first = new StringBuffer();
-                            } else {
-                                first.append(" ");
-                            }
-                            first.append(token);
-                        } else {
-                            last = token;
-                        }
-                    }
-                    if (first == null)
+        String username = formData.getFirst("username");
+
+        UserModel user = realm.getUser(username);
+        if (user != null) {
+            return Flows.forms(realm, request).setError(Messages.USERNAME_EXISTS).setFormData(formData)
+                    .setSocialRegistration(isSocialRegistration).forwardToRegistration();
+        }
+
+        user = realm.addUser(username);
+
+        String fullname = formData.getFirst("name");
+        if (fullname != null) {
+            StringTokenizer tokenizer = new StringTokenizer(fullname, " ");
+            StringBuffer first = null;
+            String last = "";
+            while (tokenizer.hasMoreTokens()) {
+                String token = tokenizer.nextToken();
+                if (tokenizer.hasMoreTokens()) {
+                    if (first == null) {
                         first = new StringBuffer();
-                    user.setFirstName(first.toString());
-                    user.setLastName(last);
+                    } else {
+                        first.append(" ");
+                    }
+                    first.append(token);
+                } else {
+                    last = token;
                 }
+            }
+            if (first == null)
+                first = new StringBuffer();
+            user.setFirstName(first.toString());
+            user.setLastName(last);
+        }
 
-                user.setEmail(formData.getFirst("email"));
+        user.setEmail(formData.getFirst("email"));
 
-                if (requiredCredentialTypes.contains(CredentialRepresentation.PASSWORD)) {
-                    UserCredentialModel credentials = new UserCredentialModel();
-                    credentials.setType(CredentialRepresentation.PASSWORD);
-                    credentials.setValue(formData.getFirst("password"));
-                    realm.updateCredential(user, credentials);
-                }
+        if (requiredCredentialTypes.contains(CredentialRepresentation.PASSWORD)) {
+            UserCredentialModel credentials = new UserCredentialModel();
+            credentials.setType(CredentialRepresentation.PASSWORD);
+            credentials.setValue(formData.getFirst("password"));
+            realm.updateCredential(user, credentials);
+        }
 
-                for (RoleModel role : realm.getDefaultRoles()) {
-                    realm.grantRole(user, role);
-                }
+        for (RoleModel role : realm.getDefaultRoles()) {
+            realm.grantRole(user, role);
+        }
 
-                return processLogin(clientId, scopeParam, state, redirect, formData);
-            }
-        }.call();
+        return processLogin(clientId, scopeParam, state, redirect, formData);
     }
 
     @Path("access/codes")
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 16fc089..8e21a21 100644
--- a/social/core/src/main/java/org/keycloak/social/AuthCallback.java
+++ b/social/core/src/main/java/org/keycloak/social/AuthCallback.java
@@ -28,16 +28,16 @@ import java.util.Map;
  */
 public class AuthCallback {
     
-    private Map<String, String> attributes;
+    private Map<String, Object> attributes;
 
     private Map<String, String[]> queryParams;
 
-    public AuthCallback(Map<String, String> attributes, Map<String, String[]> queryParams) {
+    public AuthCallback(Map<String, Object> attributes, Map<String, String[]> queryParams) {
         this.attributes = attributes;
         this.queryParams = queryParams;
     }
 
-    public String getAttribute(String name) {
+    public Object getAttribute(String name) {
         return attributes.get(name);
     }
 
diff --git a/social/core/src/main/java/org/keycloak/social/AuthRequest.java b/social/core/src/main/java/org/keycloak/social/AuthRequest.java
index a88d805..69731e9 100644
--- a/social/core/src/main/java/org/keycloak/social/AuthRequest.java
+++ b/social/core/src/main/java/org/keycloak/social/AuthRequest.java
@@ -33,9 +33,9 @@ public class AuthRequest {
 
     private URI authUri;
 
-    private Map<String, String> attributes;
+    private Map<String, Object> attributes;
 
-    AuthRequest(String id, URI authUri, Map<String, String> attributes) {
+    AuthRequest(String id, URI authUri, Map<String, Object> attributes) {
         this.id = id;
         this.authUri = authUri;
         this.attributes = attributes;
@@ -49,7 +49,7 @@ public class AuthRequest {
         return authUri;
     }
 
-    public Map<String, String> getAttributes() {
+    public Map<String, Object> getAttributes() {
         return attributes;
     }
 
diff --git a/social/core/src/main/java/org/keycloak/social/AuthRequestBuilder.java b/social/core/src/main/java/org/keycloak/social/AuthRequestBuilder.java
index 600783d..c5dd66a 100644
--- a/social/core/src/main/java/org/keycloak/social/AuthRequestBuilder.java
+++ b/social/core/src/main/java/org/keycloak/social/AuthRequestBuilder.java
@@ -33,7 +33,7 @@ public class AuthRequestBuilder {
 
     private UriBuilder b;
     
-    private Map<String, String> attributes;
+    private Map<String, Object> attributes;
 
     private String id;
 
@@ -44,7 +44,7 @@ public class AuthRequestBuilder {
         AuthRequestBuilder req = new AuthRequestBuilder();
         req.id = id;
         req.b = UriBuilder.fromUri(path);
-        req.attributes = new HashMap<String, String>();
+        req.attributes = new HashMap<String, Object>();
         return req;
     }
 
@@ -53,7 +53,7 @@ public class AuthRequestBuilder {
         return this;
     }
 
-    public AuthRequestBuilder setAttribute(String name, String value) {
+    public AuthRequestBuilder setAttribute(String name, Object value) {
         attributes.put(name, value);
         return this;
     }
diff --git a/social/core/src/main/java/org/keycloak/social/RequestDetails.java b/social/core/src/main/java/org/keycloak/social/RequestDetails.java
index 0476a64..f77f59b 100644
--- a/social/core/src/main/java/org/keycloak/social/RequestDetails.java
+++ b/social/core/src/main/java/org/keycloak/social/RequestDetails.java
@@ -32,9 +32,9 @@ public class RequestDetails {
 
     private Map<String, String> clientAttributes;
 
-    private Map<String, String> socialAttributes;
+    private Map<String, Object> socialAttributes;
 
-    RequestDetails(String providerId, Map<String, String> clientAttributes, Map<String, String> socialAttributes) {
+    RequestDetails(String providerId, Map<String, String> clientAttributes, Map<String, Object> socialAttributes) {
         this.providerId = providerId;
         this.clientAttributes = clientAttributes;
         this.socialAttributes = socialAttributes;
@@ -52,11 +52,11 @@ public class RequestDetails {
         return clientAttributes;
     }
 
-    public String getSocialAttribute(String name) {
+    public Object getSocialAttribute(String name) {
         return socialAttributes.get(name);
     }
 
-    public Map<String, String> getSocialAttributes() {
+    public Map<String, Object> getSocialAttributes() {
         return socialAttributes;
     }
 
diff --git a/social/core/src/main/java/org/keycloak/social/RequestDetailsBuilder.java b/social/core/src/main/java/org/keycloak/social/RequestDetailsBuilder.java
index a8d8286..aa86fbe 100644
--- a/social/core/src/main/java/org/keycloak/social/RequestDetailsBuilder.java
+++ b/social/core/src/main/java/org/keycloak/social/RequestDetailsBuilder.java
@@ -33,7 +33,7 @@ public class RequestDetailsBuilder {
 
     private Map<String, String> clientAttributes;
 
-    private Map<String, String> socialAttributes;
+    private Map<String, Object> socialAttributes;
 
     private RequestDetailsBuilder() {
     }
@@ -42,7 +42,17 @@ public class RequestDetailsBuilder {
         RequestDetailsBuilder req = new RequestDetailsBuilder();
         req.providerId = providerId;
         req.clientAttributes = new HashMap<String, String>();
-        req.socialAttributes = new HashMap<String, String>();
+        req.socialAttributes = new HashMap<String, Object>();
+        return req;
+    }
+
+    public static RequestDetailsBuilder createFromRequestDetails(RequestDetails from) {
+        RequestDetailsBuilder req = new RequestDetailsBuilder();
+        req.providerId = from.getProviderId();
+        req.clientAttributes = new HashMap<String, String>();
+        req.clientAttributes.putAll(from.getClientAttributes());
+        req.socialAttributes = new HashMap<String, Object>();
+        req.socialAttributes.putAll(from.getSocialAttributes());
         return req;
     }
 
@@ -56,12 +66,12 @@ public class RequestDetailsBuilder {
         return this;
     }
 
-    public RequestDetailsBuilder putSocialAttribute(String name, String value) {
+    public RequestDetailsBuilder putSocialAttribute(String name, Object value) {
         socialAttributes.put(name, value);
         return this;
     }
 
-    public RequestDetailsBuilder putSocialAttributes(Map<String, String> attributes) {
+    public RequestDetailsBuilder putSocialAttributes(Map<String, Object> attributes) {
         socialAttributes.putAll(attributes);
         return this;
     }
diff --git a/social/core/src/main/java/org/keycloak/social/SocialConstants.java b/social/core/src/main/java/org/keycloak/social/SocialConstants.java
new file mode 100644
index 0000000..f1d5cd7
--- /dev/null
+++ b/social/core/src/main/java/org/keycloak/social/SocialConstants.java
@@ -0,0 +1,11 @@
+package org.keycloak.social;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class SocialConstants {
+
+    public static final String ATTR_SOCIAL_LINK = "ATTR_SOCIAL_LINK";
+
+    public static final String SOCIAL_REGISTRATION_COOKIE = "SOCIAL_REGISTRATION_COOKIE";
+}
diff --git a/social/core/src/main/java/org/keycloak/social/SocialRequestManager.java b/social/core/src/main/java/org/keycloak/social/SocialRequestManager.java
index 2993a9a..735ea6b 100644
--- a/social/core/src/main/java/org/keycloak/social/SocialRequestManager.java
+++ b/social/core/src/main/java/org/keycloak/social/SocialRequestManager.java
@@ -57,6 +57,11 @@ public class SocialRequestManager {
 
         return details;
     }
+
+    // Just obtain data without expiring it
+    public synchronized RequestDetails getData(String requestId) {
+        return map.get(requestId);
+    }
     
     private void pruneExpired() {
         long currentTime = System.currentTimeMillis();
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 49b6e6e..54c4cc2 100644
--- a/social/twitter/src/main/java/org/keycloak/social/twitter/TwitterProvider.java
+++ b/social/twitter/src/main/java/org/keycloak/social/twitter/TwitterProvider.java
@@ -71,7 +71,7 @@ public class TwitterProvider implements SocialProvider {
             twitter.setOAuthConsumer(config.getKey(), config.getSecret());
 
             String verifier = callback.getQueryParam("oauth_verifier");
-            RequestToken requestToken = new RequestToken(callback.getAttribute("token"), callback.getAttribute("tokenSecret"));
+            RequestToken requestToken = new RequestToken((String)callback.getAttribute("token"), (String)callback.getAttribute("tokenSecret"));
 
             twitter.getOAuthAccessToken(requestToken, verifier);
             twitter4j.User twitterUser = twitter.verifyCredentials();