keycloak-aplcache

Changes

Details

diff --git a/federation/ldap/src/main/java/org/keycloak/storage/ldap/idm/store/ldap/LDAPOperationManager.java b/federation/ldap/src/main/java/org/keycloak/storage/ldap/idm/store/ldap/LDAPOperationManager.java
index d3d8317..49b8385 100644
--- a/federation/ldap/src/main/java/org/keycloak/storage/ldap/idm/store/ldap/LDAPOperationManager.java
+++ b/federation/ldap/src/main/java/org/keycloak/storage/ldap/idm/store/ldap/LDAPOperationManager.java
@@ -326,6 +326,10 @@ public class LDAPOperationManager {
             filter = "(&(objectClass=*)(" + getUuidAttributeName() + LDAPConstants.EQUAL + id + "))";
         }
 
+        if (logger.isTraceEnabled()) {
+            logger.tracef("Using filter for lookup user by LDAP ID: %s", filter);
+        }
+
         return filter;
     }
 
diff --git a/server-spi-private/src/main/java/org/keycloak/broker/provider/AuthenticationRequest.java b/server-spi-private/src/main/java/org/keycloak/broker/provider/AuthenticationRequest.java
index ba8276f..f8a810e 100644
--- a/server-spi-private/src/main/java/org/keycloak/broker/provider/AuthenticationRequest.java
+++ b/server-spi-private/src/main/java/org/keycloak/broker/provider/AuthenticationRequest.java
@@ -17,6 +17,7 @@
 package org.keycloak.broker.provider;
 
 import org.jboss.resteasy.spi.HttpRequest;
+import org.keycloak.broker.provider.util.IdentityBrokerState;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.RealmModel;
 import org.keycloak.sessions.AuthenticationSessionModel;
@@ -30,13 +31,13 @@ public class AuthenticationRequest {
 
     private final KeycloakSession session;
     private final UriInfo uriInfo;
-    private final String state;
+    private final IdentityBrokerState state;
     private final HttpRequest httpRequest;
     private final RealmModel realm;
     private final String redirectUri;
     private final AuthenticationSessionModel authSession;
 
-    public AuthenticationRequest(KeycloakSession session, RealmModel realm, AuthenticationSessionModel authSession, HttpRequest httpRequest, UriInfo uriInfo, String state, String redirectUri) {
+    public AuthenticationRequest(KeycloakSession session, RealmModel realm, AuthenticationSessionModel authSession, HttpRequest httpRequest, UriInfo uriInfo, IdentityBrokerState state, String redirectUri) {
         this.session = session;
         this.realm = realm;
         this.httpRequest = httpRequest;
@@ -54,7 +55,7 @@ public class AuthenticationRequest {
         return this.uriInfo;
     }
 
-    public String getState() {
+    public IdentityBrokerState getState() {
         return this.state;
     }
 
diff --git a/server-spi-private/src/main/java/org/keycloak/broker/provider/util/IdentityBrokerState.java b/server-spi-private/src/main/java/org/keycloak/broker/provider/util/IdentityBrokerState.java
new file mode 100644
index 0000000..c44b4c4
--- /dev/null
+++ b/server-spi-private/src/main/java/org/keycloak/broker/provider/util/IdentityBrokerState.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.broker.provider.util;
+
+import java.util.regex.Pattern;
+
+/**
+ * Encapsulates parsing logic related to state passed to identity provider in "state" (or RelayState) parameter
+ *
+ * Not Thread-safe
+ *
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class IdentityBrokerState {
+
+    private String decodedState;
+    private String clientId;
+    private String encodedState;
+
+    private IdentityBrokerState() {
+    }
+
+    public static IdentityBrokerState decoded(String decodedState, String clientId) {
+        IdentityBrokerState state = new IdentityBrokerState();
+        state.decodedState = decodedState;
+        state.clientId = clientId;
+        return state;
+    }
+
+    public static IdentityBrokerState encoded(String encodedState) {
+        IdentityBrokerState state = new IdentityBrokerState();
+        state.encodedState = encodedState;
+        return state;
+    }
+
+
+    public String getDecodedState() {
+        if (decodedState == null) {
+            decode();
+        }
+        return decodedState;
+    }
+
+    public String getClientId() {
+        if (decodedState == null) {
+            decode();
+        }
+        return clientId;
+    }
+
+    public String getEncodedState() {
+        if (encodedState == null) {
+            encode();
+        }
+        return encodedState;
+    }
+
+
+    private void decode() {
+        String[] decoded = DOT.split(encodedState, 0);
+        decodedState = decoded[0];
+        if (decoded.length > 0) {
+            clientId = decoded[1];
+        }
+    }
+
+
+    private void encode() {
+        encodedState = decodedState + "." + clientId;
+    }
+
+    private static final Pattern DOT = Pattern.compile("\\.");
+
+
+}
diff --git a/server-spi-private/src/main/java/org/keycloak/forms/login/LoginFormsProvider.java b/server-spi-private/src/main/java/org/keycloak/forms/login/LoginFormsProvider.java
index 32195ef..ac435e3 100755
--- a/server-spi-private/src/main/java/org/keycloak/forms/login/LoginFormsProvider.java
+++ b/server-spi-private/src/main/java/org/keycloak/forms/login/LoginFormsProvider.java
@@ -22,7 +22,6 @@ import org.keycloak.models.RoleModel;
 import org.keycloak.models.UserModel;
 import org.keycloak.models.utils.FormMessage;
 import org.keycloak.provider.Provider;
-import org.keycloak.sessions.AuthenticationSessionModel;
 
 import javax.ws.rs.core.MultivaluedMap;
 import javax.ws.rs.core.Response;
diff --git a/server-spi-private/src/main/java/org/keycloak/models/Constants.java b/server-spi-private/src/main/java/org/keycloak/models/Constants.java
index 260ac1d..40f9081 100755
--- a/server-spi-private/src/main/java/org/keycloak/models/Constants.java
+++ b/server-spi-private/src/main/java/org/keycloak/models/Constants.java
@@ -52,8 +52,12 @@ public interface Constants {
     int DEFAULT_OFFLINE_SESSION_IDLE_TIMEOUT = 2592000;
 
     String VERIFY_EMAIL_KEY = "VERIFY_EMAIL_KEY";
+    String EXECUTION = "execution";
+    String CLIENT_ID = "client_id";
     String KEY = "key";
 
+    String SKIP_LINK = "skipLink";
+
     // Prefix for user attributes used in various "context"data maps
     String USER_ATTRIBUTES_PREFIX = "user.attributes.";
 
diff --git a/services/src/main/java/org/keycloak/authentication/actiontoken/ActionTokenContext.java b/services/src/main/java/org/keycloak/authentication/actiontoken/ActionTokenContext.java
index a55c587..ca00b0d 100644
--- a/services/src/main/java/org/keycloak/authentication/actiontoken/ActionTokenContext.java
+++ b/services/src/main/java/org/keycloak/authentication/actiontoken/ActionTokenContext.java
@@ -45,7 +45,7 @@ public class ActionTokenContext<T extends JsonWebToken> {
 
     @FunctionalInterface
     public interface ProcessBrokerFlow {
-        Response brokerLoginFlow(String code, String execution, String flowPath);
+        Response brokerLoginFlow(String code, String execution, String clientId, String flowPath);
     };
 
     private final KeycloakSession session;
@@ -158,6 +158,7 @@ public class ActionTokenContext<T extends JsonWebToken> {
     }
 
     public Response brokerFlow(String code, String flowPath) {
-        return processBrokerFlow.brokerLoginFlow(code, getExecutionId(), flowPath);
+        ClientModel client = authenticationSession.getClient();
+        return processBrokerFlow.brokerLoginFlow(code, getExecutionId(), client.getClientId(), flowPath);
     }
 }
diff --git a/services/src/main/java/org/keycloak/authentication/actiontoken/idpverifyemail/IdpVerifyAccountLinkActionTokenHandler.java b/services/src/main/java/org/keycloak/authentication/actiontoken/idpverifyemail/IdpVerifyAccountLinkActionTokenHandler.java
index 389441e..bd56eea 100644
--- a/services/src/main/java/org/keycloak/authentication/actiontoken/idpverifyemail/IdpVerifyAccountLinkActionTokenHandler.java
+++ b/services/src/main/java/org/keycloak/authentication/actiontoken/idpverifyemail/IdpVerifyAccountLinkActionTokenHandler.java
@@ -23,6 +23,7 @@ import org.keycloak.authentication.actiontoken.*;
 import org.keycloak.authentication.authenticators.broker.IdpEmailVerificationAuthenticator;
 import org.keycloak.events.*;
 import org.keycloak.forms.login.LoginFormsProvider;
+import org.keycloak.models.Constants;
 import org.keycloak.models.UserModel;
 import org.keycloak.services.managers.AuthenticationSessionManager;
 import org.keycloak.services.messages.Messages;
@@ -86,7 +87,7 @@ public class IdpVerifyAccountLinkActionTokenHandler extends AbstractActionTokenH
 
             return tokenContext.getSession().getProvider(LoginFormsProvider.class)
                     .setSuccess(Messages.IDENTITY_PROVIDER_LINK_SUCCESS, token.getIdentityProviderAlias(), token.getIdentityProviderUsername())
-                    .setAttribute("skipLink", true)
+                    .setAttribute(Constants.SKIP_LINK, true)
                     .createInfoPage();
         }
 
diff --git a/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java b/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java
index 2427091..23d06e3 100755
--- a/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java
+++ b/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java
@@ -33,6 +33,7 @@ import org.keycloak.models.AuthenticationFlowModel;
 import org.keycloak.models.AuthenticatorConfigModel;
 import org.keycloak.models.AuthenticatedClientSessionModel;
 import org.keycloak.models.ClientModel;
+import org.keycloak.models.Constants;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.UserModel;
@@ -485,15 +486,17 @@ public class AuthenticationProcessor {
             return LoginActionsService.loginActionsBaseUrl(getUriInfo())
                     .path(AuthenticationProcessor.this.flowPath)
                     .queryParam(OAuth2Constants.CODE, code)
-                    .queryParam("execution", getExecution().getId())
+                    .queryParam(Constants.EXECUTION, getExecution().getId())
+                    .queryParam(Constants.CLIENT_ID, getAuthenticationSession().getClient().getClientId())
                     .build(getRealm().getName());
         }
 
         @Override
         public URI getActionTokenUrl(String tokenString) {
             return LoginActionsService.actionTokenProcessor(getUriInfo())
-                    .queryParam("key", tokenString)
-                    .queryParam("execution", getExecution().getId())
+                    .queryParam(Constants.KEY, tokenString)
+                    .queryParam(Constants.EXECUTION, getExecution().getId())
+                    .queryParam(Constants.CLIENT_ID, getAuthenticationSession().getClient().getClientId())
                     .build(getRealm().getName());
         }
 
@@ -501,7 +504,8 @@ public class AuthenticationProcessor {
         public URI getRefreshExecutionUrl() {
             return LoginActionsService.loginActionsBaseUrl(getUriInfo())
                     .path(AuthenticationProcessor.this.flowPath)
-                    .queryParam("execution", getExecution().getId())
+                    .queryParam(Constants.EXECUTION, getExecution().getId())
+                    .queryParam(Constants.CLIENT_ID, getAuthenticationSession().getClient().getClientId())
                     .build(getRealm().getName());
         }
 
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpEmailVerificationAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpEmailVerificationAuthenticator.java
index a2a9b3a..7189b95 100755
--- a/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpEmailVerificationAuthenticator.java
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpEmailVerificationAuthenticator.java
@@ -132,7 +132,10 @@ public class IdpEmailVerificationAuthenticator extends AbstractIdpAuthenticator 
           brokerContext.getUsername(), brokerContext.getIdpConfig().getAlias()
         );
         UriBuilder builder = Urls.actionTokenBuilder(uriInfo.getBaseUri(), token.serialize(session, realm, uriInfo));
-        String link = builder.queryParam("execution", context.getExecution().getId()).build(realm.getName()).toString();
+        String link = builder
+                .queryParam(Constants.EXECUTION, context.getExecution().getId())
+                .queryParam(Constants.CLIENT_ID, context.getExecution().getId())
+                .build(realm.getName()).toString();
         long expirationInMinutes = TimeUnit.SECONDS.toMinutes(validityInSecs);
 
         try {
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/browser/IdentityProviderAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/browser/IdentityProviderAuthenticator.java
index cb31e8d..b255ace 100644
--- a/services/src/main/java/org/keycloak/authentication/authenticators/browser/IdentityProviderAuthenticator.java
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/browser/IdentityProviderAuthenticator.java
@@ -64,8 +64,9 @@ public class IdentityProviderAuthenticator implements Authenticator {
         for (IdentityProviderModel identityProvider : identityProviders) {
             if (identityProvider.isEnabled() && providerId.equals(identityProvider.getAlias())) {
                 String accessCode = new ClientSessionCode<>(context.getSession(), context.getRealm(), context.getAuthenticationSession()).getCode();
+                String clientId = context.getAuthenticationSession().getClient().getClientId();
                 Response response = Response.seeOther(
-                        Urls.identityProviderAuthnRequest(context.getUriInfo().getBaseUri(), providerId, context.getRealm().getName(), accessCode))
+                        Urls.identityProviderAuthnRequest(context.getUriInfo().getBaseUri(), providerId, context.getRealm().getName(), accessCode, clientId))
                         .build();
 
                 LOG.debugf("Redirecting to %s", providerId);
diff --git a/services/src/main/java/org/keycloak/authentication/FormAuthenticationFlow.java b/services/src/main/java/org/keycloak/authentication/FormAuthenticationFlow.java
index 955879f..82c12ec 100755
--- a/services/src/main/java/org/keycloak/authentication/FormAuthenticationFlow.java
+++ b/services/src/main/java/org/keycloak/authentication/FormAuthenticationFlow.java
@@ -24,6 +24,8 @@ import org.keycloak.events.EventBuilder;
 import org.keycloak.forms.login.LoginFormsProvider;
 import org.keycloak.models.AuthenticationExecutionModel;
 import org.keycloak.models.AuthenticatorConfigModel;
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.Constants;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.UserModel;
@@ -247,9 +249,11 @@ public class FormAuthenticationFlow implements AuthenticationFlow {
     }
 
     public URI getActionUrl(String executionId, String code) {
+        ClientModel client = processor.getAuthenticationSession().getClient();
         return LoginActionsService.registrationFormProcessor(processor.getUriInfo())
                 .queryParam(OAuth2Constants.CODE, code)
-                .queryParam("execution", executionId)
+                .queryParam(Constants.EXECUTION, executionId)
+                .queryParam(Constants.CLIENT_ID, client.getClientId())
                 .build(processor.getRealm().getName());
     }
 
diff --git a/services/src/main/java/org/keycloak/authentication/forms/RegistrationPage.java b/services/src/main/java/org/keycloak/authentication/forms/RegistrationPage.java
index cb60861..72b602f 100755
--- a/services/src/main/java/org/keycloak/authentication/forms/RegistrationPage.java
+++ b/services/src/main/java/org/keycloak/authentication/forms/RegistrationPage.java
@@ -36,7 +36,6 @@ import java.util.List;
  */
 public class RegistrationPage implements FormAuthenticator, FormAuthenticatorFactory {
 
-    public static final String EXECUTION = "execution";
     public static final String FIELD_PASSWORD_CONFIRM = "password-confirm";
     public static final String FIELD_PASSWORD = "password";
     public static final String FIELD_EMAIL = "email";
diff --git a/services/src/main/java/org/keycloak/authentication/RequiredActionContextResult.java b/services/src/main/java/org/keycloak/authentication/RequiredActionContextResult.java
index 87b3403..1d9475a 100755
--- a/services/src/main/java/org/keycloak/authentication/RequiredActionContextResult.java
+++ b/services/src/main/java/org/keycloak/authentication/RequiredActionContextResult.java
@@ -23,6 +23,8 @@ import org.keycloak.common.ClientConnection;
 import org.keycloak.common.util.Time;
 import org.keycloak.events.EventBuilder;
 import org.keycloak.forms.login.LoginFormsProvider;
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.Constants;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.UserModel;
@@ -132,9 +134,11 @@ public class RequiredActionContextResult implements RequiredActionContext {
 
     @Override
     public URI getActionUrl(String code) {
+        ClientModel client = authenticationSession.getClient();
         return LoginActionsService.requiredActionProcessor(getUriInfo())
                 .queryParam(OAuth2Constants.CODE, code)
-                .queryParam("execution", factory.getId())
+                .queryParam(Constants.EXECUTION, factory.getId())
+                .queryParam(Constants.CLIENT_ID, client.getClientId())
                 .build(getRealm().getName());
     }
 
diff --git a/services/src/main/java/org/keycloak/broker/oidc/AbstractOAuth2IdentityProvider.java b/services/src/main/java/org/keycloak/broker/oidc/AbstractOAuth2IdentityProvider.java
index 339747a..f4a877e 100755
--- a/services/src/main/java/org/keycloak/broker/oidc/AbstractOAuth2IdentityProvider.java
+++ b/services/src/main/java/org/keycloak/broker/oidc/AbstractOAuth2IdentityProvider.java
@@ -155,7 +155,7 @@ public abstract class AbstractOAuth2IdentityProvider<C extends OAuth2IdentityPro
     protected UriBuilder createAuthorizationUrl(AuthenticationRequest request) {
         return UriBuilder.fromUri(getConfig().getAuthorizationUrl())
                 .queryParam(OAUTH2_PARAMETER_SCOPE, getConfig().getDefaultScope())
-                .queryParam(OAUTH2_PARAMETER_STATE, request.getState())
+                .queryParam(OAUTH2_PARAMETER_STATE, request.getState().getEncodedState())
                 .queryParam(OAUTH2_PARAMETER_RESPONSE_TYPE, "code")
                 .queryParam(OAUTH2_PARAMETER_CLIENT_ID, getConfig().getClientId())
                 .queryParam(OAUTH2_PARAMETER_REDIRECT_URI, request.getRedirectUri());
diff --git a/services/src/main/java/org/keycloak/broker/saml/SAMLIdentityProvider.java b/services/src/main/java/org/keycloak/broker/saml/SAMLIdentityProvider.java
index 51d6eb8..886ee4d 100755
--- a/services/src/main/java/org/keycloak/broker/saml/SAMLIdentityProvider.java
+++ b/services/src/main/java/org/keycloak/broker/saml/SAMLIdentityProvider.java
@@ -100,7 +100,7 @@ public class SAMLIdentityProvider extends AbstractIdentityProvider<SAMLIdentityP
                     .protocolBinding(protocolBinding)
                     .nameIdPolicy(SAML2NameIDPolicyBuilder.format(nameIDPolicyFormat));
             JaxrsSAML2BindingBuilder binding = new JaxrsSAML2BindingBuilder()
-                    .relayState(request.getState());
+                    .relayState(request.getState().getEncodedState());
             boolean postBinding = getConfig().isPostBindingAuthnRequest();
 
             if (getConfig().isWantAuthnRequestsSigned()) {
diff --git a/services/src/main/java/org/keycloak/forms/login/freemarker/FreeMarkerLoginFormsProvider.java b/services/src/main/java/org/keycloak/forms/login/freemarker/FreeMarkerLoginFormsProvider.java
index 625406c..affaf20 100755
--- a/services/src/main/java/org/keycloak/forms/login/freemarker/FreeMarkerLoginFormsProvider.java
+++ b/services/src/main/java/org/keycloak/forms/login/freemarker/FreeMarkerLoginFormsProvider.java
@@ -17,7 +17,6 @@
 package org.keycloak.forms.login.freemarker;
 
 import org.jboss.logging.Logger;
-import org.jboss.resteasy.specimpl.MultivaluedMapImpl;
 import org.keycloak.OAuth2Constants;
 import org.keycloak.authentication.requiredactions.util.UpdateProfileContext;
 import org.keycloak.authentication.requiredactions.util.UserUpdateProfileContext;
@@ -40,7 +39,6 @@ import org.keycloak.models.*;
 import org.keycloak.models.utils.FormMessage;
 import org.keycloak.services.Urls;
 import org.keycloak.services.messages.Messages;
-import org.keycloak.sessions.AuthenticationSessionModel;
 import org.keycloak.theme.BrowserSecurityHeaderSetup;
 import org.keycloak.theme.FreeMarkerException;
 import org.keycloak.theme.FreeMarkerUtil;
@@ -75,7 +73,6 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
     private List<RoleModel> realmRolesRequested;
     private MultivaluedMap<String, RoleModel> resourceRolesRequested;
     private List<ProtocolMapperModel> protocolMappersRequested;
-    private MultivaluedMap<String, String> queryParams;
     private Map<String, String> httpResponseHeaders = new HashMap<String, String>();
     private String accessRequestMessage;
     private URI actionUri;
@@ -146,8 +143,6 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
         ClientModel client = session.getContext().getClient();
         UriInfo uriInfo = session.getContext().getUri();
 
-        MultivaluedMap<String, String> queryParameterMap = queryParams != null ? queryParams : new MultivaluedMapImpl<String, String>();
-
         String requestURI = uriInfo.getBaseUri().getPath();
         UriBuilder uriBuilder = UriBuilder.fromUri(requestURI);
         if (page == LoginFormsPages.OAUTH_GRANT) {
@@ -155,19 +150,17 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
             uriBuilder.replaceQuery(null);
         }
 
+        if (client != null) {
+            uriBuilder.queryParam(Constants.CLIENT_ID, client.getClientId());
+        }
+
         URI baseUri = uriBuilder.build();
 
         if (accessCode != null) {
             uriBuilder.queryParam(OAuth2Constants.CODE, accessCode);
         }
-        URI baseUriWithCode = uriBuilder.build();
-
-        for (String k : queryParameterMap.keySet()) {
 
-            Object[] objects = queryParameterMap.get(k).toArray();
-            if (objects.length == 1 && objects[0] == null) continue; //
-            uriBuilder.replaceQueryParam(k, objects);
-        }
+        URI baseUriWithCodeAndClientId = uriBuilder.build();
 
         ThemeProvider themeProvider = session.getProvider(ThemeProvider.class, "extending");
         Theme theme;
@@ -220,7 +213,7 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
 
             List<IdentityProviderModel> identityProviders = realm.getIdentityProviders();
             identityProviders = LoginFormsUtil.filterIdentityProviders(identityProviders, session, realm, attributes, formData);
-            attributes.put("social", new IdentityProviderBean(realm, session, identityProviders, baseUriWithCode));
+            attributes.put("social", new IdentityProviderBean(realm, session, identityProviders, baseUriWithCodeAndClientId));
 
             attributes.put("url", new UrlBean(realm, theme, baseUri, this.actionUri));
 
@@ -301,16 +294,11 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
         ClientModel client = session.getContext().getClient();
         UriInfo uriInfo = session.getContext().getUri();
 
-        MultivaluedMap<String, String> queryParameterMap = queryParams != null ? queryParams : new MultivaluedMapImpl<String, String>();
-
         String requestURI = uriInfo.getBaseUri().getPath();
         UriBuilder uriBuilder = UriBuilder.fromUri(requestURI);
 
-        for (String k : queryParameterMap.keySet()) {
-
-            Object[] objects = queryParameterMap.get(k).toArray();
-            if (objects.length == 1 && objects[0] == null) continue; //
-            uriBuilder.replaceQueryParam(k, objects);
+        if (client != null) {
+            uriBuilder.queryParam(Constants.CLIENT_ID, client.getClientId());
         }
 
         URI baseUri = uriBuilder.build();
@@ -318,6 +306,7 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
         if (accessCode != null) {
             uriBuilder.queryParam(OAuth2Constants.CODE, accessCode);
         }
+
         URI baseUriWithCode = uriBuilder.build();
 
         ThemeProvider themeProvider = session.getProvider(ThemeProvider.class, "extending");
diff --git a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
index 31217f1..805867b 100755
--- a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
@@ -78,9 +78,6 @@ public class AuthenticationManager {
     public static final String END_AFTER_REQUIRED_ACTIONS = "END_AFTER_REQUIRED_ACTIONS";
     public static final String INVALIDATE_ACTION_TOKEN = "INVALIDATE_ACTION_TOKEN";
 
-    // Last authenticated client in userSession.
-    public static final String LAST_AUTHENTICATED_CLIENT = "LAST_AUTHENTICATED_CLIENT";
-
     // userSession note with authTime (time when authentication flow including requiredActions was finished)
     public static final String AUTH_TIME = "AUTH_TIME";
     // clientSession note with flag that clientSession was authenticated through SSO cookie
@@ -95,7 +92,6 @@ public class AuthenticationManager {
     public static final String KEYCLOAK_SESSION_COOKIE = "KEYCLOAK_SESSION";
     public static final String KEYCLOAK_REMEMBER_ME = "KEYCLOAK_REMEMBER_ME";
     public static final String KEYCLOAK_LOGOUT_PROTOCOL = "KEYCLOAK_LOGOUT_PROTOCOL";
-    public static final String CURRENT_REQUIRED_ACTION = "CURRENT_REQUIRED_ACTION";
 
     public static boolean isSessionValid(RealmModel realm, UserSessionModel userSession) {
         if (userSession == null) {
@@ -463,8 +459,6 @@ public class AuthenticationManager {
             userSession.setNote(AUTH_TIME, String.valueOf(authTime));
         }
 
-        userSession.setNote(LAST_AUTHENTICATED_CLIENT, clientSession.getClient().getId());
-
         return protocol.authenticated(userSession, clientSession);
 
     }
@@ -496,9 +490,11 @@ public class AuthenticationManager {
                 .path(LoginActionsService.REQUIRED_ACTION);
 
         if (requiredAction != null) {
-            uriBuilder.queryParam("execution", requiredAction);
+            uriBuilder.queryParam(Constants.EXECUTION, requiredAction);
         }
 
+        uriBuilder.queryParam(Constants.CLIENT_ID, authSession.getClient().getClientId());
+
         URI redirect = uriBuilder.build(realm.getName());
         return Response.status(302).location(redirect).build();
 
@@ -526,7 +522,7 @@ public class AuthenticationManager {
                 }
 
             } else {
-                infoPage.setAttribute("skipLink", true);
+                infoPage.setAttribute(Constants.SKIP_LINK, true);
             }
             Response response = infoPage
                     .createInfoPage();
diff --git a/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java b/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java
index 333a1cf..530fce2 100755
--- a/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java
+++ b/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java
@@ -30,6 +30,7 @@ import org.keycloak.broker.provider.IdentityBrokerException;
 import org.keycloak.broker.provider.IdentityProvider;
 import org.keycloak.broker.provider.IdentityProviderFactory;
 import org.keycloak.broker.provider.IdentityProviderMapper;
+import org.keycloak.broker.provider.util.IdentityBrokerState;
 import org.keycloak.broker.saml.SAMLEndpoint;
 import org.keycloak.broker.social.SocialIdentityProvider;
 import org.keycloak.common.ClientConnection;
@@ -338,14 +339,14 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
 
     @POST
     @Path("/{provider_id}/login")
-    public Response performPostLogin(@PathParam("provider_id") String providerId, @QueryParam("code") String code) {
-        return performLogin(providerId, code);
+    public Response performPostLogin(@PathParam("provider_id") String providerId, @QueryParam("code") String code, @QueryParam("client_id") String clientId) {
+        return performLogin(providerId, code, clientId);
     }
 
     @GET
     @NoCache
     @Path("/{provider_id}/login")
-    public Response performLogin(@PathParam("provider_id") String providerId, @QueryParam("code") String code) {
+    public Response performLogin(@PathParam("provider_id") String providerId, @QueryParam("code") String code, @QueryParam("client_id") String clientId) {
         this.event.detail(Details.IDENTITY_PROVIDER, providerId);
 
         if (isDebugEnabled()) {
@@ -353,7 +354,7 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
         }
 
         try {
-            ParsedCodeContext parsedCode = parseClientSessionCode(code);
+            ParsedCodeContext parsedCode = parseSessionCode(code, clientId);
             if (parsedCode.response != null) {
                 return parsedCode.response;
             }
@@ -479,7 +480,7 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
         if (context.getContextData().get(SAMLEndpoint.SAML_IDP_INITIATED_CLIENT_ID) != null) {
             parsedCode = samlIdpInitiatedSSO((String) context.getContextData().get(SAMLEndpoint.SAML_IDP_INITIATED_CLIENT_ID));
         } else {
-            parsedCode = parseClientSessionCode(context.getCode());
+            parsedCode = parseEncodedSessionCode(context.getCode());
         }
         if (parsedCode.response != null) {
             return parsedCode.response;
@@ -549,6 +550,7 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
             ctx.saveToAuthenticationSession(authenticationSession, AbstractIdpAuthenticator.BROKERED_CONTEXT_NOTE);
 
             URI redirect = LoginActionsService.firstBrokerLoginProcessor(uriInfo)
+                    .queryParam(Constants.CLIENT_ID, authenticationSession.getClient().getClientId())
                     .build(realmModel.getName());
             return Response.status(302).location(redirect).build();
 
@@ -584,8 +586,8 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
     @GET
     @NoCache
     @Path("/after-first-broker-login")
-    public Response afterFirstBrokerLogin(@QueryParam("code") String code) {
-        ParsedCodeContext parsedCode = parseClientSessionCode(code);
+    public Response afterFirstBrokerLogin(@QueryParam("code") String code, @QueryParam("client_id") String clientId) {
+        ParsedCodeContext parsedCode = parseSessionCode(code, clientId);
         if (parsedCode.response != null) {
             return parsedCode.response;
         }
@@ -701,6 +703,7 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
             authSession.setAuthNote(PostBrokerLoginConstants.PBL_AFTER_FIRST_BROKER_LOGIN, String.valueOf(wasFirstBrokerLogin));
 
             URI redirect = LoginActionsService.postBrokerLoginProcessor(uriInfo)
+                    .queryParam(Constants.CLIENT_ID, authSession.getClient().getClientId())
                     .build(realmModel.getName());
             return Response.status(302).location(redirect).build();
         }
@@ -711,8 +714,8 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
     @GET
     @NoCache
     @Path("/after-post-broker-login")
-    public Response afterPostBrokerLoginFlow(@QueryParam("code") String code) {
-        ParsedCodeContext parsedCode = parseClientSessionCode(code);
+    public Response afterPostBrokerLoginFlow(@QueryParam("code") String code, @QueryParam("client_id") String clientId) {
+        ParsedCodeContext parsedCode = parseSessionCode(code, clientId);
         if (parsedCode.response != null) {
             return parsedCode.response;
         }
@@ -804,7 +807,7 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
 
     @Override
     public Response cancelled(String code) {
-        ParsedCodeContext parsedCode = parseClientSessionCode(code);
+        ParsedCodeContext parsedCode = parseEncodedSessionCode(code);
         if (parsedCode.response != null) {
             return parsedCode.response;
         }
@@ -820,7 +823,7 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
 
     @Override
     public Response error(String code, String message) {
-        ParsedCodeContext parsedCode = parseClientSessionCode(code);
+        ParsedCodeContext parsedCode = parseEncodedSessionCode(code);
         if (parsedCode.response != null) {
             return parsedCode.response;
         }
@@ -960,14 +963,21 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
         }
     }
 
-    private ParsedCodeContext parseClientSessionCode(String code) {
-        if (code == null) {
-            logger.debugf("Invalid request. Authorization code was null");
+    private ParsedCodeContext parseEncodedSessionCode(String encodedCode) {
+        IdentityBrokerState state = IdentityBrokerState.encoded(encodedCode);
+        String code = state.getDecodedState();
+        String clientId = state.getClientId();
+        return parseSessionCode(code, clientId);
+    }
+
+    private ParsedCodeContext parseSessionCode(String code, String clientId) {
+        if (code == null || clientId == null) {
+            logger.debugf("Invalid request. Authorization code or clientId was null. Code=" + code + ", clientId=" + clientId);
             Response staleCodeError = redirectToErrorPage(Messages.INVALID_REQUEST);
             return ParsedCodeContext.response(staleCodeError);
         }
 
-        SessionCodeChecks checks = new SessionCodeChecks(realmModel, uriInfo, clientConnection, session, event, code, null, LoginActionsService.AUTHENTICATE_PATH);
+        SessionCodeChecks checks = new SessionCodeChecks(realmModel, uriInfo, clientConnection, session, event, code, null, clientId, LoginActionsService.AUTHENTICATE_PATH);
         checks.initialVerify();
         if (!checks.verifyActiveAndValidAction(AuthenticationSessionModel.Action.AUTHENTICATE.name(), ClientSessionCode.ActionType.LOGIN)) {
 
@@ -1041,14 +1051,15 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
 
     private AuthenticationRequest createAuthenticationRequest(String providerId, ClientSessionCode<AuthenticationSessionModel> clientSessionCode) {
         AuthenticationSessionModel authSession = null;
-        String relayState = null;
+        IdentityBrokerState encodedState = null;
 
         if (clientSessionCode != null) {
             authSession = clientSessionCode.getClientSession();
-            relayState = clientSessionCode.getCode();
+            String relayState = clientSessionCode.getCode();
+            encodedState = IdentityBrokerState.decoded(relayState, authSession.getClient().getClientId());
         }
 
-        return new AuthenticationRequest(this.session, this.realmModel, authSession, this.request, this.uriInfo, relayState, getRedirectUri(providerId));
+        return new AuthenticationRequest(this.session, this.realmModel, authSession, this.request, this.uriInfo, encodedState, getRedirectUri(providerId));
     }
 
     private String getRedirectUri(String providerId) {
diff --git a/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java b/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java
index 8f0d39e..e436412 100755
--- a/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java
+++ b/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java
@@ -179,16 +179,16 @@ public class LoginActionsService {
         }
     }
 
-    private SessionCodeChecks checksForCode(String code, String execution, String flowPath) {
-        SessionCodeChecks res = new SessionCodeChecks(realm, uriInfo, clientConnection, session, event, code, execution, flowPath);
+    private SessionCodeChecks checksForCode(String code, String execution, String clientId, String flowPath) {
+        SessionCodeChecks res = new SessionCodeChecks(realm, uriInfo, clientConnection, session, event, code, execution, clientId, flowPath);
         res.initialVerify();
         return res;
     }
 
 
-    protected URI getLastExecutionUrl(String flowPath, String executionId) {
+    protected URI getLastExecutionUrl(String flowPath, String executionId, String clientId) {
         return new AuthenticationFlowURLHelper(session, realm, uriInfo)
-                .getLastExecutionUrl(flowPath, executionId);
+                .getLastExecutionUrl(flowPath, executionId, clientId);
     }
 
 
@@ -199,9 +199,9 @@ public class LoginActionsService {
      */
     @Path(RESTART_PATH)
     @GET
-    public Response restartSession() {
+    public Response restartSession(@QueryParam("client_id") String clientId) {
         event.event(EventType.RESTART_AUTHENTICATION);
-        SessionCodeChecks checks = new SessionCodeChecks(realm, uriInfo, clientConnection, session, event, null, null, null);
+        SessionCodeChecks checks = new SessionCodeChecks(realm, uriInfo, clientConnection, session, event, null, null, clientId, null);
 
         AuthenticationSessionModel authSession = checks.initialVerifyAuthSession();
         if (authSession == null) {
@@ -215,7 +215,7 @@ public class LoginActionsService {
 
         AuthenticationProcessor.resetFlow(authSession, flowPath);
 
-        URI redirectUri = getLastExecutionUrl(flowPath, null);
+        URI redirectUri = getLastExecutionUrl(flowPath, null, authSession.getClient().getClientId());
         logger.debugf("Flow restart requested. Redirecting to %s", redirectUri);
         return Response.status(Response.Status.FOUND).location(redirectUri).build();
     }
@@ -230,10 +230,11 @@ public class LoginActionsService {
     @Path(AUTHENTICATE_PATH)
     @GET
     public Response authenticate(@QueryParam("code") String code,
-                                 @QueryParam("execution") String execution) {
+                                 @QueryParam("execution") String execution,
+                                 @QueryParam("client_id") String clientId) {
         event.event(EventType.LOGIN);
 
-        SessionCodeChecks checks = checksForCode(code, execution, AUTHENTICATE_PATH);
+        SessionCodeChecks checks = checksForCode(code, execution, clientId, AUTHENTICATE_PATH);
         if (!checks.verifyActiveAndValidAction(ClientSessionModel.Action.AUTHENTICATE.name(), ClientSessionCode.ActionType.LOGIN)) {
             return checks.getResponse();
         }
@@ -298,22 +299,24 @@ public class LoginActionsService {
     @Path(AUTHENTICATE_PATH)
     @POST
     public Response authenticateForm(@QueryParam("code") String code,
-                                     @QueryParam("execution") String execution) {
-        return authenticate(code, execution);
+                                     @QueryParam("execution") String execution,
+                                     @QueryParam("client_id") String clientId) {
+        return authenticate(code, execution, clientId);
     }
 
     @Path(RESET_CREDENTIALS_PATH)
     @POST
     public Response resetCredentialsPOST(@QueryParam("code") String code,
                                          @QueryParam("execution") String execution,
+                                         @QueryParam("client_id") String clientId,
                                          @QueryParam(Constants.KEY) String key) {
         if (key != null) {
-            return handleActionToken(key, execution);
+            return handleActionToken(key, execution, clientId);
         }
 
         event.event(EventType.RESET_PASSWORD);
 
-        return resetCredentials(code, execution);
+        return resetCredentials(code, execution, clientId);
     }
 
     /**
@@ -327,7 +330,8 @@ public class LoginActionsService {
     @Path(RESET_CREDENTIALS_PATH)
     @GET
     public Response resetCredentialsGET(@QueryParam("code") String code,
-                                        @QueryParam("execution") String execution) {
+                                        @QueryParam("execution") String execution,
+                                        @QueryParam("client_id") String clientId) {
         AuthenticationSessionModel authSession = new AuthenticationSessionManager(session).getCurrentAuthenticationSession(realm);
 
         // we allow applications to link to reset credentials without going through OAuth or SAML handshakes
@@ -343,7 +347,7 @@ public class LoginActionsService {
         }
 
         event.event(EventType.RESET_PASSWORD);
-        return resetCredentials(code, execution);
+        return resetCredentials(code, execution, clientId);
     }
 
     AuthenticationSessionModel createAuthenticationSessionForClient()
@@ -370,8 +374,8 @@ public class LoginActionsService {
      * @param execution
      * @return
      */
-    protected Response resetCredentials(String code, String execution) {
-        SessionCodeChecks checks = checksForCode(code, execution, RESET_CREDENTIALS_PATH);
+    protected Response resetCredentials(String code, String execution, String clientId) {
+        SessionCodeChecks checks = checksForCode(code, execution, clientId, RESET_CREDENTIALS_PATH);
         if (!checks.verifyActiveAndValidAction(ClientSessionModel.Action.AUTHENTICATE.name(), ClientSessionCode.ActionType.USER)) {
             return checks.getResponse();
         }
@@ -397,11 +401,12 @@ public class LoginActionsService {
     @Path("action-token")
     @GET
     public Response executeActionToken(@QueryParam("key") String key,
-                                       @QueryParam("execution") String execution) {
-        return handleActionToken(key, execution);
+                                       @QueryParam("execution") String execution,
+                                       @QueryParam("client_id") String clientId) {
+        return handleActionToken(key, execution, clientId);
     }
 
-    protected <T extends DefaultActionToken> Response handleActionToken(String tokenString, String execution) {
+    protected <T extends DefaultActionToken> Response handleActionToken(String tokenString, String execution, String clientId) {
         T token;
         ActionTokenHandler<T> handler;
         ActionTokenContext<T> tokenContext;
@@ -411,6 +416,15 @@ public class LoginActionsService {
 
         event.event(EventType.EXECUTE_ACTION_TOKEN);
 
+        // Setup client, so error page will contain "back to application" link
+        ClientModel client = null;
+        if (clientId != null) {
+            client = realm.getClientByClientId(clientId);
+        }
+        if (client != null) {
+            session.getContext().setClient(client);
+        }
+
         // First resolve action token handler
         try {
             if (tokenString == null) {
@@ -570,8 +584,9 @@ public class LoginActionsService {
     @Path(REGISTRATION_PATH)
     @GET
     public Response registerPage(@QueryParam("code") String code,
-                                 @QueryParam("execution") String execution) {
-        return registerRequest(code, execution, false);
+                                 @QueryParam("execution") String execution,
+                                 @QueryParam("client_id") String clientId) {
+        return registerRequest(code, execution, clientId, false);
     }
 
 
@@ -584,19 +599,20 @@ public class LoginActionsService {
     @Path(REGISTRATION_PATH)
     @POST
     public Response processRegister(@QueryParam("code") String code,
-                                    @QueryParam("execution") String execution) {
-        return registerRequest(code, execution, true);
+                                    @QueryParam("execution") String execution,
+                                    @QueryParam("client_id") String clientId) {
+        return registerRequest(code, execution, clientId, true);
     }
 
 
-    private Response registerRequest(String code, String execution, boolean isPostRequest) {
+    private Response registerRequest(String code, String execution, String clientId, boolean isPostRequest) {
         event.event(EventType.REGISTER);
         if (!realm.isRegistrationAllowed()) {
             event.error(Errors.REGISTRATION_DISABLED);
             return ErrorPage.error(session, Messages.REGISTRATION_NOT_ALLOWED);
         }
 
-        SessionCodeChecks checks = checksForCode(code, execution, REGISTRATION_PATH);
+        SessionCodeChecks checks = checksForCode(code, execution, clientId, REGISTRATION_PATH);
         if (!checks.verifyActiveAndValidAction(ClientSessionModel.Action.AUTHENTICATE.name(), ClientSessionCode.ActionType.LOGIN)) {
             return checks.getResponse();
         }
@@ -612,39 +628,43 @@ public class LoginActionsService {
     @Path(FIRST_BROKER_LOGIN_PATH)
     @GET
     public Response firstBrokerLoginGet(@QueryParam("code") String code,
-                                 @QueryParam("execution") String execution) {
-        return brokerLoginFlow(code, execution, FIRST_BROKER_LOGIN_PATH);
+                                        @QueryParam("execution") String execution,
+                                        @QueryParam("client_id") String clientId) {
+        return brokerLoginFlow(code, execution, clientId, FIRST_BROKER_LOGIN_PATH);
     }
 
     @Path(FIRST_BROKER_LOGIN_PATH)
     @POST
     public Response firstBrokerLoginPost(@QueryParam("code") String code,
-                                        @QueryParam("execution") String execution) {
-        return brokerLoginFlow(code, execution, FIRST_BROKER_LOGIN_PATH);
+                                         @QueryParam("execution") String execution,
+                                         @QueryParam("client_id") String clientId) {
+        return brokerLoginFlow(code, execution, clientId, FIRST_BROKER_LOGIN_PATH);
     }
 
     @Path(POST_BROKER_LOGIN_PATH)
     @GET
     public Response postBrokerLoginGet(@QueryParam("code") String code,
-                                       @QueryParam("execution") String execution) {
-        return brokerLoginFlow(code, execution, POST_BROKER_LOGIN_PATH);
+                                       @QueryParam("execution") String execution,
+                                       @QueryParam("client_id") String clientId) {
+        return brokerLoginFlow(code, execution, clientId, POST_BROKER_LOGIN_PATH);
     }
 
     @Path(POST_BROKER_LOGIN_PATH)
     @POST
     public Response postBrokerLoginPost(@QueryParam("code") String code,
-                                        @QueryParam("execution") String execution) {
-        return brokerLoginFlow(code, execution, POST_BROKER_LOGIN_PATH);
+                                        @QueryParam("execution") String execution,
+                                        @QueryParam("client_id") String clientId) {
+        return brokerLoginFlow(code, execution, clientId, POST_BROKER_LOGIN_PATH);
     }
 
 
-    protected Response brokerLoginFlow(String code, String execution, String flowPath) {
+    protected Response brokerLoginFlow(String code, String execution, String clientId, String flowPath) {
         boolean firstBrokerLogin = flowPath.equals(FIRST_BROKER_LOGIN_PATH);
 
         EventType eventType = firstBrokerLogin ? EventType.IDENTITY_PROVIDER_FIRST_LOGIN : EventType.IDENTITY_PROVIDER_POST_LOGIN;
         event.event(eventType);
 
-        SessionCodeChecks checks = checksForCode(code, execution, flowPath);
+        SessionCodeChecks checks = checksForCode(code, execution, clientId, flowPath);
         if (!checks.verifyActiveAndValidAction(ClientSessionModel.Action.AUTHENTICATE.name(), ClientSessionCode.ActionType.LOGIN)) {
             return checks.getResponse();
         }
@@ -702,8 +722,9 @@ public class LoginActionsService {
         ClientSessionCode<AuthenticationSessionModel> accessCode = new ClientSessionCode<>(session, realm, authSession);
         authSession.setTimestamp(Time.currentTime());
 
-        URI redirect = firstBrokerLogin ? Urls.identityProviderAfterFirstBrokerLogin(uriInfo.getBaseUri(), realm.getName(), accessCode.getCode()) :
-                Urls.identityProviderAfterPostBrokerLogin(uriInfo.getBaseUri(), realm.getName(), accessCode.getCode()) ;
+        String clientId = authSession.getClient().getClientId();
+        URI redirect = firstBrokerLogin ? Urls.identityProviderAfterFirstBrokerLogin(uriInfo.getBaseUri(), realm.getName(), accessCode.getCode(), clientId) :
+                Urls.identityProviderAfterPostBrokerLogin(uriInfo.getBaseUri(), realm.getName(), accessCode.getCode(), clientId) ;
         logger.debugf("Redirecting to '%s' ", redirect);
 
         return Response.status(302).location(redirect).build();
@@ -722,7 +743,8 @@ public class LoginActionsService {
     public Response processConsent(final MultivaluedMap<String, String> formData) {
         event.event(EventType.LOGIN);
         String code = formData.getFirst("code");
-        SessionCodeChecks checks = checksForCode(code, null, REQUIRED_ACTION);
+        String clientId = uriInfo.getQueryParameters().getFirst(Constants.CLIENT_ID);
+        SessionCodeChecks checks = checksForCode(code, null, clientId, REQUIRED_ACTION);
         if (!checks.verifyRequiredAction(ClientSessionModel.Action.OAUTH_GRANT.name())) {
             return checks.getResponse();
         }
@@ -811,21 +833,23 @@ public class LoginActionsService {
     @Path(REQUIRED_ACTION)
     @POST
     public Response requiredActionPOST(@QueryParam("code") final String code,
-                                       @QueryParam("execution") String action) {
-        return processRequireAction(code, action);
+                                       @QueryParam("execution") String action,
+                                       @QueryParam("client_id") String clientId) {
+        return processRequireAction(code, action, clientId);
     }
 
     @Path(REQUIRED_ACTION)
     @GET
     public Response requiredActionGET(@QueryParam("code") final String code,
-                                       @QueryParam("execution") String action) {
-        return processRequireAction(code, action);
+                                      @QueryParam("execution") String action,
+                                      @QueryParam("client_id") String clientId) {
+        return processRequireAction(code, action, clientId);
     }
 
-    private Response processRequireAction(final String code, String action) {
+    private Response processRequireAction(final String code, String action, String clientId) {
         event.event(EventType.CUSTOM_REQUIRED_ACTION);
 
-        SessionCodeChecks checks = checksForCode(code, action, REQUIRED_ACTION);
+        SessionCodeChecks checks = checksForCode(code, action, clientId, REQUIRED_ACTION);
         if (!checks.verifyRequiredAction(action)) {
             return checks.getResponse();
         }
diff --git a/services/src/main/java/org/keycloak/services/resources/LoginActionsServiceChecks.java b/services/src/main/java/org/keycloak/services/resources/LoginActionsServiceChecks.java
index 87eaf20..3c29ede 100644
--- a/services/src/main/java/org/keycloak/services/resources/LoginActionsServiceChecks.java
+++ b/services/src/main/java/org/keycloak/services/resources/LoginActionsServiceChecks.java
@@ -120,16 +120,8 @@ public class LoginActionsServiceChecks {
             LoginFormsProvider loginForm = context.getSession().getProvider(LoginFormsProvider.class)
               .setSuccess(Messages.ALREADY_LOGGED_IN);
 
-            ClientModel client = null;
-            String lastClientUuid = userSession.getNote(AuthenticationManager.LAST_AUTHENTICATED_CLIENT);
-            if (lastClientUuid != null) {
-                client = context.getRealm().getClientById(lastClientUuid);
-            }
-
-            if (client != null) {
-                context.getSession().getContext().setClient(client);
-            } else {
-                loginForm.setAttribute("skipLink", true);
+            if (context.getSession().getContext().getClient() == null) {
+                loginForm.setAttribute(Constants.SKIP_LINK, true);
             }
 
             throw new LoginActionsServiceException(loginForm.createInfoPage());
diff --git a/services/src/main/java/org/keycloak/services/resources/SessionCodeChecks.java b/services/src/main/java/org/keycloak/services/resources/SessionCodeChecks.java
index 978ad6f..941fa5c 100644
--- a/services/src/main/java/org/keycloak/services/resources/SessionCodeChecks.java
+++ b/services/src/main/java/org/keycloak/services/resources/SessionCodeChecks.java
@@ -33,6 +33,7 @@ import org.keycloak.events.EventBuilder;
 import org.keycloak.forms.login.LoginFormsProvider;
 import org.keycloak.models.ClientModel;
 import org.keycloak.models.ClientSessionModel;
+import org.keycloak.models.Constants;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.UserSessionModel;
@@ -66,10 +67,11 @@ public class SessionCodeChecks {
 
     private final String code;
     private final String execution;
+    private final String clientId;
     private final String flowPath;
 
 
-    public SessionCodeChecks(RealmModel realm, UriInfo uriInfo, ClientConnection clientConnection, KeycloakSession session, EventBuilder event, String code, String execution, String flowPath) {
+    public SessionCodeChecks(RealmModel realm, UriInfo uriInfo, ClientConnection clientConnection, KeycloakSession session, EventBuilder event, String code, String execution, String clientId, String flowPath) {
         this.realm = realm;
         this.uriInfo = uriInfo;
         this.clientConnection = clientConnection;
@@ -78,6 +80,7 @@ public class SessionCodeChecks {
 
         this.code = code;
         this.execution = execution;
+        this.clientId = clientId;
         this.flowPath = flowPath;
     }
 
@@ -134,6 +137,16 @@ public class SessionCodeChecks {
             return authSession;
         }
 
+        // Setup client to be shown on error/info page based on "client_id" parameter
+        logger.debugf("Will use client '%s' in back-to-application link", clientId);
+        ClientModel client = null;
+        if (clientId != null) {
+            client = realm.getClientByClientId(clientId);
+        }
+        if (client != null) {
+            session.getContext().setClient(client);
+        }
+
         // See if we are already authenticated and userSession with same ID exists.
         String sessionId = new AuthenticationSessionManager(session).getCurrentAuthenticationSessionId(realm);
         if (sessionId != null) {
@@ -143,16 +156,8 @@ public class SessionCodeChecks {
                 LoginFormsProvider loginForm = session.getProvider(LoginFormsProvider.class)
                         .setSuccess(Messages.ALREADY_LOGGED_IN);
 
-                ClientModel client = null;
-                String lastClientUuid = userSession.getNote(AuthenticationManager.LAST_AUTHENTICATED_CLIENT);
-                if (lastClientUuid != null) {
-                    client = realm.getClientById(lastClientUuid);
-                }
-
-                if (client != null) {
-                    session.getContext().setClient(client);
-                } else {
-                    loginForm.setAttribute("skipLink", true);
+                if (client == null) {
+                    loginForm.setAttribute(Constants.SKIP_LINK, true);
                 }
 
                 response = loginForm.createInfoPage();
@@ -234,7 +239,7 @@ public class SessionCodeChecks {
                 // In case that is replayed action, but sent to the same FORM like actual FORM, we just re-render the page
                 if (ObjectUtil.isEqualOrBothNull(execution, authSession.getAuthNote(AuthenticationProcessor.CURRENT_AUTHENTICATION_EXECUTION))) {
                     String latestFlowPath = authSession.getAuthNote(AuthenticationProcessor.CURRENT_FLOW_PATH);
-                    URI redirectUri = getLastExecutionUrl(latestFlowPath, execution);
+                    URI redirectUri = getLastExecutionUrl(latestFlowPath, execution, client.getClientId());
 
                     logger.debugf("Invalid action code, but execution matches. So just redirecting to %s", redirectUri);
                     authSession.setAuthNote(LoginActionsService.FORWARDED_ERROR_MESSAGE_NOTE, Messages.EXPIRED_ACTION);
@@ -289,7 +294,7 @@ public class SessionCodeChecks {
 
             authSession.setAuthNote(LoginActionsService.FORWARDED_ERROR_MESSAGE_NOTE, Messages.LOGIN_TIMEOUT);
 
-            URI redirectUri = getLastExecutionUrl(LoginActionsService.AUTHENTICATE_PATH, null);
+            URI redirectUri = getLastExecutionUrl(LoginActionsService.AUTHENTICATE_PATH, null, authSession.getClient().getClientId());
             logger.debugf("Flow restart after timeout. Redirecting to %s", redirectUri);
             response = Response.status(Response.Status.FOUND).location(redirectUri).build();
             return false;
@@ -351,7 +356,7 @@ public class SessionCodeChecks {
                 flowPath = LoginActionsService.AUTHENTICATE_PATH;
             }
 
-            URI redirectUri = getLastExecutionUrl(flowPath, null);
+            URI redirectUri = getLastExecutionUrl(flowPath, null, authSession.getClient().getClientId());
             logger.debugf("Authentication session restart from cookie succeeded. Redirecting to %s", redirectUri);
             return Response.status(Response.Status.FOUND).location(redirectUri).build();
         } else {
@@ -367,16 +372,20 @@ public class SessionCodeChecks {
                 .path(LoginActionsService.REQUIRED_ACTION);
 
         if (action != null) {
-            uriBuilder.queryParam("execution", action);
+            uriBuilder.queryParam(Constants.EXECUTION, action);
         }
+
+        ClientModel client = authSession.getClient();
+        uriBuilder.queryParam(Constants.CLIENT_ID, client.getClientId());
+
         URI redirect = uriBuilder.build(realm.getName());
         return Response.status(302).location(redirect).build();
     }
 
 
-    private URI getLastExecutionUrl(String flowPath, String executionId) {
+    private URI getLastExecutionUrl(String flowPath, String executionId, String clientId) {
         return new AuthenticationFlowURLHelper(session, realm, uriInfo)
-                .getLastExecutionUrl(flowPath, executionId);
+                .getLastExecutionUrl(flowPath, executionId, clientId);
     }
 
 
diff --git a/services/src/main/java/org/keycloak/services/Urls.java b/services/src/main/java/org/keycloak/services/Urls.java
index e92aa05..51f505e 100755
--- a/services/src/main/java/org/keycloak/services/Urls.java
+++ b/services/src/main/java/org/keycloak/services/Urls.java
@@ -18,6 +18,7 @@ package org.keycloak.services;
 
 import org.keycloak.OAuth2Constants;
 import org.keycloak.common.Version;
+import org.keycloak.models.Constants;
 import org.keycloak.protocol.oidc.OIDCLoginProtocol;
 import org.keycloak.protocol.oidc.OIDCLoginProtocolService;
 import org.keycloak.services.resources.AccountService;
@@ -73,13 +74,16 @@ public class Urls {
                 .build(realmName, providerId);
     }
 
-    public static URI identityProviderAuthnRequest(URI baseUri, String providerId, String realmName, String accessCode) {
+    public static URI identityProviderAuthnRequest(URI baseUri, String providerId, String realmName, String accessCode, String clientId) {
         UriBuilder uriBuilder = realmBase(baseUri).path(RealmsResource.class, "getBrokerService")
                 .path(IdentityBrokerService.class, "performLogin");
 
         if (accessCode != null) {
             uriBuilder.replaceQueryParam(OAuth2Constants.CODE, accessCode);
         }
+        if (clientId != null) {
+            uriBuilder.replaceQueryParam(Constants.CLIENT_ID, clientId);
+        }
 
         return uriBuilder.build(realmName, providerId);
     }
@@ -99,20 +103,22 @@ public class Urls {
     }
 
     public static URI identityProviderAuthnRequest(URI baseURI, String providerId, String realmName) {
-        return identityProviderAuthnRequest(baseURI, providerId, realmName, null);
+        return identityProviderAuthnRequest(baseURI, providerId, realmName, null, null);
     }
 
-    public static URI identityProviderAfterFirstBrokerLogin(URI baseUri, String realmName, String accessCode) {
+    public static URI identityProviderAfterFirstBrokerLogin(URI baseUri, String realmName, String accessCode, String clientId) {
         return realmBase(baseUri).path(RealmsResource.class, "getBrokerService")
                 .path(IdentityBrokerService.class, "afterFirstBrokerLogin")
                 .replaceQueryParam(OAuth2Constants.CODE, accessCode)
+                .replaceQueryParam(Constants.CLIENT_ID, clientId)
                 .build(realmName);
     }
 
-    public static URI identityProviderAfterPostBrokerLogin(URI baseUri, String realmName, String accessCode) {
+    public static URI identityProviderAfterPostBrokerLogin(URI baseUri, String realmName, String accessCode, String clientId) {
         return realmBase(baseUri).path(RealmsResource.class, "getBrokerService")
                 .path(IdentityBrokerService.class, "afterPostBrokerLoginFlow")
                 .replaceQueryParam(OAuth2Constants.CODE, accessCode)
+                .replaceQueryParam(Constants.CLIENT_ID, clientId)
                 .build(realmName);
     }
 
diff --git a/services/src/main/java/org/keycloak/services/util/AuthenticationFlowURLHelper.java b/services/src/main/java/org/keycloak/services/util/AuthenticationFlowURLHelper.java
index b97963e..3726b99 100644
--- a/services/src/main/java/org/keycloak/services/util/AuthenticationFlowURLHelper.java
+++ b/services/src/main/java/org/keycloak/services/util/AuthenticationFlowURLHelper.java
@@ -26,6 +26,7 @@ import javax.ws.rs.core.UriInfo;
 import org.jboss.logging.Logger;
 import org.keycloak.authentication.AuthenticationProcessor;
 import org.keycloak.forms.login.LoginFormsProvider;
+import org.keycloak.models.Constants;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.RealmModel;
 import org.keycloak.protocol.AuthorizationEndpointBase;
@@ -61,13 +62,15 @@ public class AuthenticationFlowURLHelper {
     }
 
 
-    public URI getLastExecutionUrl(String flowPath, String executionId) {
+    public URI getLastExecutionUrl(String flowPath, String executionId, String clientId) {
         UriBuilder uriBuilder = LoginActionsService.loginActionsBaseUrl(uriInfo)
                 .path(flowPath);
 
         if (executionId != null) {
-            uriBuilder.queryParam("execution", executionId);
+            uriBuilder.queryParam(Constants.EXECUTION, executionId);
         }
+        uriBuilder.queryParam(Constants.CLIENT_ID, clientId);
+
         return uriBuilder.build(realm.getName());
     }
 
@@ -84,7 +87,7 @@ public class AuthenticationFlowURLHelper {
             latestFlowPath = LoginActionsService.AUTHENTICATE_PATH;
         }
 
-        return getLastExecutionUrl(latestFlowPath, executionId);
+        return getLastExecutionUrl(latestFlowPath, executionId, authSession.getClient().getClientId());
     }
 
 }
diff --git a/services/src/main/java/org/keycloak/social/twitter/TwitterIdentityProvider.java b/services/src/main/java/org/keycloak/social/twitter/TwitterIdentityProvider.java
index 00be27c..77009e7 100755
--- a/services/src/main/java/org/keycloak/social/twitter/TwitterIdentityProvider.java
+++ b/services/src/main/java/org/keycloak/social/twitter/TwitterIdentityProvider.java
@@ -74,7 +74,7 @@ public class TwitterIdentityProvider extends AbstractIdentityProvider<OAuth2Iden
             Twitter twitter = new TwitterFactory().getInstance();
             twitter.setOAuthConsumer(getConfig().getClientId(), getConfig().getClientSecret());
 
-            URI uri = new URI(request.getRedirectUri() + "?state=" + request.getState());
+            URI uri = new URI(request.getRedirectUri() + "?state=" + request.getState().getEncodedState());
 
             RequestToken requestToken = twitter.getOAuthRequestToken(uri.toString());
             AuthenticationSessionModel authSession = request.getAuthenticationSession();
diff --git a/testsuite/integration/src/test/resources/log4j.properties b/testsuite/integration/src/test/resources/log4j.properties
index 2c6e884..6439950 100755
--- a/testsuite/integration/src/test/resources/log4j.properties
+++ b/testsuite/integration/src/test/resources/log4j.properties
@@ -82,8 +82,13 @@ log4j.logger.org.apache.directory.server.ldap.LdapProtocolHandler=error
 #log4j.logger.org.apache.http.impl.conn=debug
 
 # Enable to view details from identity provider authenticator
-log4j.logger.org.keycloak.authentication.authenticators.browser.IdentityProviderAuthenticator=trace
-log4j.logger.org.keycloak.services.resources.IdentityBrokerService=trace
-log4j.logger.org.keycloak.broker=trace
+#log4j.logger.org.keycloak.authentication.authenticators.browser.IdentityProviderAuthenticator=trace
+#log4j.logger.org.keycloak.services.resources.IdentityBrokerService=trace
+#log4j.logger.org.keycloak.broker=trace
 
-# log4j.logger.io.undertow=trace
+#log4j.logger.io.undertow=trace
+
+#log4j.logger.org.keycloak.protocol=debug
+#log4j.logger.org.keycloak.services.resources.LoginActionsService=debug
+#log4j.logger.org.keycloak.services.managers=debug
+#log4j.logger.org.keycloak.services.resources.SessionCodeChecks=debug
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/ActionURIUtils.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/ActionURIUtils.java
new file mode 100644
index 0000000..7c8a99c
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/ActionURIUtils.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.testsuite;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import javax.ws.rs.core.UriBuilder;
+
+/**
+ * Helper for parse action-uri from the HTML login page and do something with it (eg. open in new browser, parse code parameter and use it somewhere else etc)
+ *
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class ActionURIUtils {
+
+    private static final Pattern ACTION_URI_PATTERN = Pattern.compile("action=\"([^\"]+)\"");
+
+    private static final Pattern QUERY_STRING_PATTERN = Pattern.compile("[^\\?]+\\?([^#]+).*");
+
+    private static final Pattern PARAMS_PATTERN = Pattern.compile("[=\\&]");
+
+    public static String getActionURIFromPageSource(String htmlPageSource) {
+        Matcher m = ACTION_URI_PATTERN.matcher(htmlPageSource);
+        if (m.find()) {
+            return m.group(1).replaceAll("&amp;", "&");
+        } else {
+            return null;
+        }
+    }
+
+    public static Map<String, String> parseQueryParamsFromActionURI(String actionURI) {
+        Matcher m = QUERY_STRING_PATTERN.matcher(actionURI);
+        if (m.find()) {
+            String queryString = m.group(1);
+
+            String[] params = PARAMS_PATTERN.split(queryString, 0);
+            Map<String, String> result = new HashMap<>(); // Don't take multivalued into account for now
+
+            for (int i=0 ; i<params.length ; i+=2) {
+                String paramName = params[i];
+                String paramValue = params[i+1];
+                result.put(paramName, paramValue);
+            }
+            return result;
+        } else {
+            return Collections.emptyMap();
+        }
+    }
+
+    public static String removeQueryParamFromURI(String actionURI, String paramName) {
+        return UriBuilder.fromUri(actionURI)
+                .replaceQueryParam(paramName, null)
+                .build().toString();
+    }
+
+
+    /*
+    private static final String TEST = "<form id=\"kc-form-login\" class=\"form-horizontal\" action=\"http://localhost:8180/auth/realms/child/login-actions/authenticate?code=1WnqOmapgo0cj3mpRQ-vbleIKUJdwFzonzy1fjvnWQQ&amp;execution=3ac92a20-9c31-49de-a3c8-f2a4fff80986&amp;client_id=client-linking\" method=\"post\">";
+
+    public static void main(String[] args) {
+        String actionURI = getActionURIFromPageSource(TEST);
+        System.out.println("action uri: " + actionURI);
+
+        Map<String, String> params = parseQueryParamsFromActionURI(actionURI);
+        System.out.println("params: " + params);
+
+        String actionURI2 = removeQueryParamFromURI(actionURI, "execution");
+        System.out.println("action uri 2: " + actionURI2);
+    }*/
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/ErrorPage.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/ErrorPage.java
index 5b4a116..fc1c078 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/ErrorPage.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/ErrorPage.java
@@ -43,6 +43,14 @@ public class ErrorPage extends AbstractPage {
         backToApplicationLink.click();
     }
 
+    public String getBackToApplicationLink() {
+        if (backToApplicationLink == null) {
+            return null;
+        } else {
+            return backToApplicationLink.getAttribute("href");
+        }
+    }
+
     public boolean isCurrent() {
         return driver.getTitle() != null && driver.getTitle().equals("We're sorry...");
     }
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/actions/RequiredActionUpdateProfileTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/actions/RequiredActionUpdateProfileTest.java
index 9c18070..d829d5b 100755
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/actions/RequiredActionUpdateProfileTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/actions/RequiredActionUpdateProfileTest.java
@@ -25,6 +25,7 @@ import org.junit.Test;
 import org.keycloak.events.Details;
 import org.keycloak.events.EventType;
 import org.keycloak.models.UserModel;
+import org.keycloak.representations.idm.ClientRepresentation;
 import org.keycloak.representations.idm.RealmRepresentation;
 import org.keycloak.representations.idm.UserRepresentation;
 import org.keycloak.testsuite.AssertEvents;
@@ -32,6 +33,7 @@ import org.keycloak.testsuite.AbstractTestRealmKeycloakTest;
 import org.keycloak.testsuite.admin.ApiUtil;
 import org.keycloak.testsuite.pages.AppPage;
 import org.keycloak.testsuite.pages.AppPage.RequestType;
+import org.keycloak.testsuite.pages.ErrorPage;
 import org.keycloak.testsuite.pages.LoginPage;
 import org.keycloak.testsuite.pages.LoginUpdateProfileEditUsernameAllowedPage;
 import org.keycloak.testsuite.util.UserBuilder;
@@ -53,6 +55,9 @@ public class RequiredActionUpdateProfileTest extends AbstractTestRealmKeycloakTe
     @Page
     protected LoginUpdateProfileEditUsernameAllowedPage updateProfilePage;
 
+    @Page
+    protected ErrorPage errorPage;
+
     @Override
     public void configureTestRealm(RealmRepresentation testRealm) {
         ActionUtil.addRequiredActionForUser(testRealm, "test-user@localhost", UserModel.RequiredAction.UPDATE_PROFILE.name());
@@ -294,4 +299,23 @@ public class RequiredActionUpdateProfileTest extends AbstractTestRealmKeycloakTe
         events.assertEmpty();
     }
 
+    @Test
+    public void updateProfileExpiredCookies() {
+        loginPage.open();
+        loginPage.login("john-doh@localhost", "password");
+
+        updateProfilePage.assertCurrent();
+
+        // Expire cookies and assert the page with "back to application" link present
+        driver.manage().deleteAllCookies();
+
+        updateProfilePage.update("New first", "New last", "keycloak-user@localhost", "test-user@localhost");
+        errorPage.assertCurrent();
+
+        String backToAppLink = errorPage.getBackToApplicationLink();
+
+        ClientRepresentation client = ApiUtil.findClientByClientId(adminClient.realm("test"), "test-app").toRepresentation();
+        Assert.assertEquals(backToAppLink, client.getBaseUrl());
+    }
+
 }
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractClientInitiatedAccountLinkTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractClientInitiatedAccountLinkTest.java
index 750df03..ea9937e 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractClientInitiatedAccountLinkTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractClientInitiatedAccountLinkTest.java
@@ -24,6 +24,7 @@ import org.jboss.shrinkwrap.api.spec.WebArchive;
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
+import org.keycloak.OAuth2Constants;
 import org.keycloak.admin.client.resource.ClientResource;
 import org.keycloak.admin.client.resource.RealmResource;
 import org.keycloak.common.util.Base64Url;
@@ -37,6 +38,7 @@ import org.keycloak.representations.idm.IdentityProviderRepresentation;
 import org.keycloak.representations.idm.RealmRepresentation;
 import org.keycloak.representations.idm.RoleRepresentation;
 import org.keycloak.representations.idm.UserRepresentation;
+import org.keycloak.testsuite.ActionURIUtils;
 import org.keycloak.testsuite.adapter.AbstractServletsAdapterTest;
 import org.keycloak.testsuite.admin.ApiUtil;
 import org.keycloak.testsuite.arquillian.AuthServerTestEnricher;
@@ -57,6 +59,7 @@ import javax.ws.rs.core.UriBuilder;
 import java.net.URL;
 import java.util.LinkedList;
 import java.util.List;
+import java.util.Map;
 import java.util.UUID;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
@@ -496,23 +499,11 @@ public abstract class AbstractClientInitiatedAccountLinkTest extends AbstractSer
 
             // ok, now scrape the code from page
             String pageSource = driver.getPageSource();
-            Pattern p = Pattern.compile("action=\"(.+)\"");
-            Matcher m = p.matcher(pageSource);
-            String action = null;
-            if (m.find()) {
-                action = m.group(1);
+            String action = ActionURIUtils.getActionURIFromPageSource(pageSource);
+            System.out.println("action uri: " + action);
 
-            }
-            System.out.println("action: " + action);
-
-            p = Pattern.compile("code=(.+)&");
-            m = p.matcher(action);
-            String code = null;
-            if (m.find()) {
-                code = m.group(1);
-
-            }
-            System.out.println("code: " + code);
+            Map<String, String> queryParams = ActionURIUtils.parseQueryParamsFromActionURI(action);
+            System.out.println("query params: " + queryParams);
 
             // now try and use the code to login to remote link-only idp
 
@@ -520,7 +511,8 @@ public abstract class AbstractClientInitiatedAccountLinkTest extends AbstractSer
 
             uri = UriBuilder.fromUri(AuthServerTestEnricher.getAuthServerContextRoot())
                     .path(uri)
-                    .queryParam("code", code)
+                    .queryParam(OAuth2Constants.CODE, queryParams.get(OAuth2Constants.CODE))
+                    .queryParam(Constants.CLIENT_ID, queryParams.get(Constants.CLIENT_ID))
                     .build().toString();
 
             System.out.println("hack uri: " + uri);
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/AbstractBrokerTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/AbstractBrokerTest.java
index 8129243..6500ad0 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/AbstractBrokerTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/AbstractBrokerTest.java
@@ -387,4 +387,22 @@ public abstract class AbstractBrokerTest extends AbstractBaseBrokerTest {
         logoutFromRealm(bc.providerRealmName());
         logoutFromRealm(bc.consumerRealmName());
     }
+
+
+    // KEYCLOAK-4016
+    @Test
+    public void testExpiredCode() {
+        driver.navigate().to(getAccountUrl(bc.consumerRealmName()));
+
+        log.debug("Expire all browser cookies");
+        driver.manage().deleteAllCookies();
+
+        log.debug("Clicking social " + bc.getIDPAlias());
+        accountLoginPage.clickSocial(bc.getIDPAlias());
+
+        waitForPage(driver, "sorry");
+        errorPage.assertCurrent();
+        String link = errorPage.getBackToApplicationLink();
+        Assert.assertTrue(link.endsWith("/auth/realms/consumer/account"));
+    }
 }
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/kerberos/KerberosStandaloneTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/kerberos/KerberosStandaloneTest.java
index 2d6d3af..148d7e1 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/kerberos/KerberosStandaloneTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/kerberos/KerberosStandaloneTest.java
@@ -43,6 +43,7 @@ import org.keycloak.representations.idm.ComponentRepresentation;
 import org.keycloak.representations.idm.UserRepresentation;
 import org.keycloak.storage.UserStorageProvider;
 import org.keycloak.storage.UserStorageProviderModel;
+import org.keycloak.testsuite.ActionURIUtils;
 import org.keycloak.testsuite.util.KerberosRule;
 
 /**
@@ -156,10 +157,7 @@ public class KerberosStandaloneTest extends AbstractKerberosTest {
 
         Assert.assertTrue(context.contains("Log in to test"));
 
-        Pattern pattern = Pattern.compile("action=\"([^\"]+)\"");
-        Matcher m = pattern.matcher(context);
-        Assert.assertTrue(m.find());
-        String url = m.group(1);
+        String url = ActionURIUtils.getActionURIFromPageSource(context);
 
 
         // Follow login with HttpClient. Improve if needed
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/LoginTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/LoginTest.java
index 0700677..907d4ab 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/LoginTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/LoginTest.java
@@ -27,6 +27,7 @@ import org.keycloak.events.Errors;
 import org.keycloak.events.EventType;
 import org.keycloak.models.BrowserSecurityHeaders;
 import org.keycloak.models.Constants;
+import org.keycloak.representations.idm.ClientRepresentation;
 import org.keycloak.representations.idm.EventRepresentation;
 import org.keycloak.representations.idm.RealmRepresentation;
 import org.keycloak.representations.idm.UserRepresentation;
@@ -54,6 +55,7 @@ import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
+import static org.keycloak.testsuite.admin.ApiUtil.findClientByClientId;
 
 /**
  * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
@@ -600,9 +602,13 @@ public class LoginTest extends AbstractTestRealmKeycloakTest {
 
         driver.manage().deleteAllCookies();
 
-        // Cookies are expired including KC_RESTART. No way to continue login. Error page must be shown
+        // Cookies are expired including KC_RESTART. No way to continue login. Error page must be shown with the "back to application" link
         loginPage.login("login@test.com", "password");
         errorPage.assertCurrent();
+        String link = errorPage.getBackToApplicationLink();
+
+        ClientRepresentation thirdParty = findClientByClientId(adminClient.realm("test"), "third-party").toRepresentation();
+        Assert.assertNotNull(link, thirdParty.getBaseUrl());
     }
 
 
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/MultipleTabsLoginTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/MultipleTabsLoginTest.java
index 24a70fd..9d914bd 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/MultipleTabsLoginTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/MultipleTabsLoginTest.java
@@ -27,6 +27,7 @@ import org.keycloak.models.UserModel;
 import org.keycloak.representations.idm.RealmRepresentation;
 import org.keycloak.representations.idm.UserRepresentation;
 import org.keycloak.testsuite.AbstractTestRealmKeycloakTest;
+import org.keycloak.testsuite.ActionURIUtils;
 import org.keycloak.testsuite.Assert;
 import org.keycloak.testsuite.AssertEvents;
 import org.keycloak.testsuite.admin.ApiUtil;
@@ -120,22 +121,16 @@ public class MultipleTabsLoginTest extends AbstractTestRealmKeycloakTest {
     public void openMultipleTabs() {
         oauth.openLoginForm();
         loginPage.assertCurrent();
-        String actionUrl1 = getActionUrl(driver.getPageSource());
+        String actionUrl1 = ActionURIUtils.getActionURIFromPageSource(driver.getPageSource());
 
         oauth.openLoginForm();
         loginPage.assertCurrent();
-        String actionUrl2 = getActionUrl(driver.getPageSource());
+        String actionUrl2 = ActionURIUtils.getActionURIFromPageSource(driver.getPageSource());
 
         Assert.assertEquals(actionUrl1, actionUrl2);
 
     }
 
-
-    private String getActionUrl(String pageSource) {
-        return pageSource.split("action=\"")[1].split("\"")[0].replaceAll("&amp;", "&");
-    }
-
-
     @Test
     public void multipleTabsParallelLoginTest() {
         oauth.openLoginForm();
@@ -173,7 +168,7 @@ public class MultipleTabsLoginTest extends AbstractTestRealmKeycloakTest {
         // Simulate to open login form in 2 tabs
         oauth.openLoginForm();
         loginPage.assertCurrent();
-        String actionUrl1 = getActionUrl(driver.getPageSource());
+        String actionUrl1 = ActionURIUtils.getActionURIFromPageSource(driver.getPageSource());
 
         // Click "register" in tab2
         loginPage.clickRegister();
@@ -204,7 +199,7 @@ public class MultipleTabsLoginTest extends AbstractTestRealmKeycloakTest {
         // Simulate to open login form in 2 tabs
         oauth.openLoginForm();
         loginPage.assertCurrent();
-        String actionUrl1 = getActionUrl(driver.getPageSource());
+        String actionUrl1 = ActionURIUtils.getActionURIFromPageSource(driver.getPageSource());
 
         loginPage.login("invalid", "invalid");
         loginPage.assertCurrent();
@@ -228,7 +223,7 @@ public class MultipleTabsLoginTest extends AbstractTestRealmKeycloakTest {
         // Open tab1
         oauth.openLoginForm();
         loginPage.assertCurrent();
-        String actionUrl1 = getActionUrl(driver.getPageSource());
+        String actionUrl1 = ActionURIUtils.getActionURIFromPageSource(driver.getPageSource());
 
         // Authenticate in tab2
         loginPage.login("login-test", "password");
@@ -253,8 +248,8 @@ public class MultipleTabsLoginTest extends AbstractTestRealmKeycloakTest {
         oauth.openLoginForm();
 
         // Manually remove execution from the URL and try to simulate the request just with "code" parameter
-        String actionUrl = driver.getPageSource().split("action=\"")[1].split("\"")[0].replaceAll("&amp;", "&");
-        actionUrl = actionUrl.replaceFirst("&execution=.*", "");
+        String actionUrl = ActionURIUtils.getActionURIFromPageSource(driver.getPageSource());
+        actionUrl = ActionURIUtils.removeQueryParamFromURI(actionUrl, Constants.EXECUTION);
 
         driver.navigate().to(actionUrl);
 
@@ -272,8 +267,8 @@ public class MultipleTabsLoginTest extends AbstractTestRealmKeycloakTest {
         updatePasswordPage.assertCurrent();
 
         // Manually remove execution from the URL and try to simulate the request just with "code" parameter
-        String actionUrl = driver.getPageSource().split("action=\"")[1].split("\"")[0].replaceAll("&amp;", "&");
-        actionUrl = actionUrl.replaceFirst("&execution=.*", "");
+        String actionUrl = ActionURIUtils.getActionURIFromPageSource(driver.getPageSource());
+        actionUrl = ActionURIUtils.removeQueryParamFromURI(actionUrl, Constants.EXECUTION);
 
         driver.navigate().to(actionUrl);
 
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/ResetPasswordTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/ResetPasswordTest.java
index 04ee911..16d85ef 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/ResetPasswordTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/ResetPasswordTest.java
@@ -418,6 +418,51 @@ public class ResetPasswordTest extends AbstractTestRealmKeycloakTest {
         }
     }
 
+    // KEYCLOAK-4016
+    @Test
+    public void resetPasswordExpiredCodeAndAuthSession() throws IOException, MessagingException, InterruptedException {
+        final AtomicInteger originalValue = new AtomicInteger();
+
+        RealmRepresentation realmRep = testRealm().toRepresentation();
+        originalValue.set(realmRep.getActionTokenGeneratedByUserLifespan());
+        realmRep.setActionTokenGeneratedByUserLifespan(60);
+        testRealm().update(realmRep);
+
+        try {
+            initiateResetPasswordFromResetPasswordPage("login-test");
+
+            events.expectRequiredAction(EventType.SEND_RESET_PASSWORD)
+                    .session((String)null)
+                    .user(userId).detail(Details.USERNAME, "login-test").detail(Details.EMAIL, "login@test.com").assertEvent();
+
+            assertEquals(1, greenMail.getReceivedMessages().length);
+
+            MimeMessage message = greenMail.getReceivedMessages()[0];
+
+            String changePasswordUrl = getPasswordResetEmailLink(message);
+
+            setTimeOffset(70);
+
+            log.debug("Going to reset password URI.");
+            driver.navigate().to(oauth.AUTH_SERVER_ROOT + "/realms/test/login-actions/reset-credentials"); // This is necessary to delete KC_RESTART cookie that is restricted to /auth/realms/test path
+            log.debug("Removing cookies.");
+            driver.manage().deleteAllCookies();
+            driver.navigate().to(changePasswordUrl.trim());
+
+            errorPage.assertCurrent();
+            Assert.assertEquals("Reset Credential not allowed", errorPage.getError());
+            String backToAppLink = errorPage.getBackToApplicationLink();
+            Assert.assertTrue(backToAppLink.endsWith("/app/auth"));
+
+            events.expectRequiredAction(EventType.EXECUTE_ACTION_TOKEN_ERROR).error("expired_code").client((String) null).user(userId).session((String) null).clearDetails().detail(Details.ACTION, ResetCredentialsActionToken.TOKEN_TYPE).assertEvent();
+        } finally {
+            setTimeOffset(0);
+
+            realmRep.setActionTokenGeneratedByUserLifespan(originalValue.get());
+            testRealm().update(realmRep);
+        }
+    }
+
     @Test
     public void resetPasswordDisabledUser() throws IOException, MessagingException, InterruptedException {
         UserRepresentation user = findUser("login-test");
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/AccessTokenTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/AccessTokenTest.java
index ecf540c..5209ef5 100755
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/AccessTokenTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/AccessTokenTest.java
@@ -56,6 +56,7 @@ import org.keycloak.representations.idm.RealmRepresentation;
 import org.keycloak.representations.idm.RoleRepresentation;
 import org.keycloak.representations.idm.UserRepresentation;
 import org.keycloak.testsuite.AbstractKeycloakTest;
+import org.keycloak.testsuite.ActionURIUtils;
 import org.keycloak.testsuite.AssertEvents;
 import org.keycloak.testsuite.arquillian.AuthServerTestEnricher;
 import org.keycloak.testsuite.util.ClientBuilder;
@@ -210,7 +211,8 @@ public class AccessTokenTest extends AbstractKeycloakTest {
         oauth.redirectUri(AuthServerTestEnricher.getAuthServerContextRoot() + "/auth/admin/test/console/nosuch.html");
         oauth.openLoginForm();
 
-        String loginPageCode = driver.getPageSource().split("code=")[1].split("&")[0].split("\"")[0];
+        String actionURI = ActionURIUtils.getActionURIFromPageSource(driver.getPageSource());
+        String loginPageCode = ActionURIUtils.parseQueryParamsFromActionURI(actionURI).get("code");
 
         oauth.fillLoginForm("test-user@localhost", "password");
 
@@ -441,7 +443,8 @@ public class AccessTokenTest extends AbstractKeycloakTest {
 
         oauth.doLogin("test-user@localhost", "password");
 
-        String code = driver.getPageSource().split("code=")[1].split("&")[0].split("\"")[0];
+        String actionURI = ActionURIUtils.getActionURIFromPageSource(driver.getPageSource());
+        String code = ActionURIUtils.parseQueryParamsFromActionURI(actionURI).get("code");
 
         OAuthClient.AccessTokenResponse response = oauth.doAccessTokenRequest(code, "password");
         Assert.assertEquals(400, response.getStatusCode());
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/LoginStatusIframeEndpointTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/LoginStatusIframeEndpointTest.java
index 7a01e4e..84144f6 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/LoginStatusIframeEndpointTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/LoginStatusIframeEndpointTest.java
@@ -36,6 +36,7 @@ import org.keycloak.models.Constants;
 import org.keycloak.representations.idm.ClientRepresentation;
 import org.keycloak.representations.idm.RealmRepresentation;
 import org.keycloak.testsuite.AbstractKeycloakTest;
+import org.keycloak.testsuite.ActionURIUtils;
 
 import java.io.IOException;
 import java.net.URLEncoder;
@@ -71,10 +72,7 @@ public class LoginStatusIframeEndpointTest extends AbstractKeycloakTest {
             String s = IOUtils.toString(response.getEntity().getContent());
             response.close();
 
-            Matcher matcher = Pattern.compile("action=\"([^\"]*)\"").matcher(s);
-            matcher.find();
-
-            String action = matcher.group(1);
+            String action = ActionURIUtils.getActionURIFromPageSource(s);
 
             HttpPost post = new HttpPost(action);
 
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/OAuthGrantTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/OAuthGrantTest.java
index c5304ff..926ff69 100755
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/OAuthGrantTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/OAuthGrantTest.java
@@ -42,6 +42,7 @@ import org.keycloak.testsuite.account.AccountTest;
 import org.keycloak.testsuite.admin.ApiUtil;
 import org.keycloak.testsuite.pages.AccountApplicationsPage;
 import org.keycloak.testsuite.pages.AppPage;
+import org.keycloak.testsuite.pages.ErrorPage;
 import org.keycloak.testsuite.pages.OAuthGrantPage;
 import org.keycloak.testsuite.util.ClientManager;
 import org.keycloak.testsuite.util.OAuthClient;
@@ -77,6 +78,9 @@ public class OAuthGrantTest extends AbstractKeycloakTest {
     @Page
     protected AppPage appPage;
 
+    @Page
+    protected ErrorPage errorPage;
+
     @Override
     public void addTestRealms(List<RealmRepresentation> testRealms) {
 
@@ -405,4 +409,23 @@ public class OAuthGrantTest extends AbstractKeycloakTest {
 
     }
 
+    @Test
+    public void oauthGrantExpiredAuthSession() throws Exception {
+        oauth.clientId(THIRD_PARTY_APP);
+        oauth.doLoginGrant("test-user@localhost", "password");
+
+        grantPage.assertCurrent();
+
+        // Expire cookies
+        driver.manage().deleteAllCookies();
+
+        grantPage.accept();
+
+        // Assert link "back to application" present
+        errorPage.assertCurrent();
+        String backToAppLink = errorPage.getBackToApplicationLink();
+        ClientRepresentation thirdParty = findClientByClientId(adminClient.realm(REALM_NAME), THIRD_PARTY_APP).toRepresentation();
+        Assert.assertEquals(backToAppLink, thirdParty.getBaseUrl());
+    }
+
 }
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/testrealm.json b/testsuite/integration-arquillian/tests/base/src/test/resources/testrealm.json
index edb0f61..f4b118e 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/resources/testrealm.json
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/testrealm.json
@@ -162,6 +162,7 @@
       "enabled": true,
       "consentRequired": true,
 
+      "baseUrl": "http://localhost:8180/auth/realms/master/app/auth",
       "redirectUris": [
         "http://localhost:8180/auth/realms/master/app/*"
       ],