keycloak-uncached

rev

10/10/2017 10:09:51 AM

Changes

pom.xml 1(+1 -0)

Details

diff --git a/adapters/oidc/js/src/main/resources/keycloak.js b/adapters/oidc/js/src/main/resources/keycloak.js
index ab6f788..8c5e03d 100755
--- a/adapters/oidc/js/src/main/resources/keycloak.js
+++ b/adapters/oidc/js/src/main/resources/keycloak.js
@@ -50,7 +50,7 @@
             } else if (initOptions && initOptions.adapter === 'default') {
                 adapter = loadAdapter();
             } else {
-                if (window.Cordova) {
+                if (window.Cordova || window.cordova) {
                     adapter = loadAdapter('cordova');
                 } else {
                     adapter = loadAdapter();
@@ -948,7 +948,14 @@
 
             if (type == 'cordova') {
                 loginIframe.enable = false;
-
+                var cordovaOpenWindowWrapper = function(loginUrl, target, options) {
+                    if (window.cordova && window.cordova.InAppBrowser) {
+                        // Use inappbrowser for IOS and Android if available
+                        return window.cordova.InAppBrowser.open(loginUrl, target, options);
+                    } else {
+                        return window.open(loginUrl, target, options);
+                    }
+                };
                 return {
                     login: function(options) {
                         var promise = createPromise();
@@ -959,8 +966,7 @@
                         }
 
                         var loginUrl = kc.createLoginUrl(options);
-                        var ref = window.open(loginUrl, '_blank', o);
-
+                        var ref = cordovaOpenWindowWrapper(loginUrl, '_blank', o);
                         var completed = false;
 
                         ref.addEventListener('loadstart', function(event) {
@@ -993,7 +999,7 @@
                         var promise = createPromise();
 
                         var logoutUrl = kc.createLogoutUrl(options);
-                        var ref = window.open(logoutUrl, '_blank', 'location=no,hidden=yes');
+                        var ref = cordovaOpenWindowWrapper(logoutUrl, '_blank', 'location=no,hidden=yes');
 
                         var error;
 
@@ -1026,7 +1032,7 @@
 
                     register : function() {
                         var registerUrl = kc.createRegisterUrl();
-                        var ref = window.open(registerUrl, '_blank', 'location=no');
+                        var ref = cordovaOpenWindowWrapper(registerUrl, '_blank', 'location=no');
                         ref.addEventListener('loadstart', function(event) {
                             if (event.url.indexOf('http://localhost') == 0) {
                                 ref.close();
@@ -1036,7 +1042,7 @@
 
                     accountManagement : function() {
                         var accountUrl = kc.createAccountUrl();
-                        var ref = window.open(accountUrl, '_blank', 'location=no');
+                        var ref = cordovaOpenWindowWrapper(accountUrl, '_blank', 'location=no');
                         ref.addEventListener('loadstart', function(event) {
                             if (event.url.indexOf('http://localhost') == 0) {
                                 ref.close();
diff --git a/core/src/test/java/org/keycloak/jose/JWETest.java b/core/src/test/java/org/keycloak/jose/JWETest.java
index f97f3c5..31d8a8a 100644
--- a/core/src/test/java/org/keycloak/jose/JWETest.java
+++ b/core/src/test/java/org/keycloak/jose/JWETest.java
@@ -94,7 +94,7 @@ public class JWETest {
     }
 
 
-    // @Test
+    //@Test
     public void testPerfDirect() throws Exception {
         int iterations = 50000;
 
diff --git a/core/src/test/java/org/keycloak/RSAVerifierTest.java b/core/src/test/java/org/keycloak/RSAVerifierTest.java
index e606e35..8418f14 100755
--- a/core/src/test/java/org/keycloak/RSAVerifierTest.java
+++ b/core/src/test/java/org/keycloak/RSAVerifierTest.java
@@ -28,6 +28,7 @@ import org.keycloak.common.VerificationException;
 import org.keycloak.common.util.Time;
 import org.keycloak.jose.jws.JWSBuilder;
 import org.keycloak.representations.AccessToken;
+import org.keycloak.util.JsonSerialization;
 import org.keycloak.util.TokenUtil;
 
 import javax.security.auth.x500.X500Principal;
@@ -126,28 +127,28 @@ public class RSAVerifierTest {
         return RSATokenVerifier.verifyToken(encoded, idpPair.getPublic(), "http://localhost:8080/auth/realm");
     }
 
-   /*
-   @Test
+
+   // @Test
    public void testSpeed() throws Exception
    {
-
-      byte[] tokenBytes = JsonSerialization.toByteArray(token, false);
-
-      String encoded = new JWSBuilder()
-              .content(tokenBytes)
-              .rsa256(idpPair.getPrivate());
+       // Took 44 seconds with 50000 iterations
+      byte[] tokenBytes = JsonSerialization.writeValueAsBytes(token);
 
       long start = System.currentTimeMillis();
-      int count = 10000;
+      int count = 50000;
       for (int i = 0; i < count; i++)
       {
-         SkeletonKeyTokenVerification v = RSATokenVerifier.verify(null, encoded, metadata);
+          String encoded = new JWSBuilder()
+                  .content(tokenBytes)
+                  .rsa256(idpPair.getPrivate());
+
+          verifySkeletonKeyToken(encoded);
 
       }
       long end = System.currentTimeMillis() - start;
-      System.out.println("rate: " + ((double)end/(double)count));
+      System.out.println("took: " + end);
    }
-   */
+
 
 
     @Test
diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/ActionTokenValueEntity.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/ActionTokenValueEntity.java
index 7e3c76e..647a9df 100644
--- a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/ActionTokenValueEntity.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/ActionTokenValueEntity.java
@@ -45,6 +45,11 @@ public class ActionTokenValueEntity implements ActionTokenValueModel {
         return notes.get(name);
     }
 
+    @Override
+    public String toString() {
+        return String.format("ActionTokenValueEntity [ notes=%s ]", notes.toString());
+    }
+
     public static class ExternalizerImpl implements Externalizer<ActionTokenValueEntity> {
 
         private static final int VERSION_1 = 1;
diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanActionTokenStoreProvider.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanActionTokenStoreProvider.java
index 192c964..2a31453 100644
--- a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanActionTokenStoreProvider.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanActionTokenStoreProvider.java
@@ -16,6 +16,7 @@
  */
 package org.keycloak.models.sessions.infinispan;
 
+import org.jboss.logging.Logger;
 import org.keycloak.cluster.ClusterProvider;
 import org.keycloak.common.util.Time;
 import org.keycloak.models.*;
@@ -33,6 +34,8 @@ import org.infinispan.Cache;
  */
 public class InfinispanActionTokenStoreProvider implements ActionTokenStoreProvider {
 
+    private static final Logger LOG = Logger.getLogger(InfinispanActionTokenStoreProvider.class);
+
     private final Cache<ActionTokenReducedKey, ActionTokenValueEntity> actionKeyCache;
     private final InfinispanKeycloakTransaction tx;
     private final KeycloakSession session;
@@ -58,6 +61,8 @@ public class InfinispanActionTokenStoreProvider implements ActionTokenStoreProvi
         ActionTokenReducedKey tokenKey = new ActionTokenReducedKey(key.getUserId(), key.getActionId(), key.getActionVerificationNonce());
         ActionTokenValueEntity tokenValue = new ActionTokenValueEntity(notes);
 
+        LOG.debugf("Adding used action token to actionTokens cache: %s", tokenKey.toString());
+
         this.tx.put(actionKeyCache, tokenKey, tokenValue, key.getExpiration() - Time.currentTime(), TimeUnit.SECONDS);
     }
 
@@ -68,7 +73,15 @@ public class InfinispanActionTokenStoreProvider implements ActionTokenStoreProvi
         }
 
         ActionTokenReducedKey key = new ActionTokenReducedKey(actionTokenKey.getUserId(), actionTokenKey.getActionId(), actionTokenKey.getActionVerificationNonce());
-        return this.actionKeyCache.getAdvancedCache().get(key);
+
+        ActionTokenValueModel value = this.actionKeyCache.getAdvancedCache().get(key);
+        if (value == null) {
+            LOG.debugf("Not found any value in actionTokens cache for key: %s", key.toString());
+        } else {
+            LOG.debugf("Found value in actionTokens cache for key: %s", key.toString());
+        }
+
+        return value;
     }
     
     @Override

pom.xml 1(+1 -0)

diff --git a/pom.xml b/pom.xml
index 43d6145..87b288b 100755
--- a/pom.xml
+++ b/pom.xml
@@ -1433,6 +1433,7 @@
                     <configuration>
                         <forkMode>once</forkMode>
                         <argLine>${surefire.memory.settings}</argLine>
+                        <argLine>-Djava.awt.headless=true</argLine>
                     </configuration>
                 </plugin>
                 <plugin>
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 cb203c1..3f44b1e 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,6 +22,7 @@ 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;
@@ -74,6 +75,8 @@ public interface LoginFormsProvider extends Provider {
     public Response createOAuthGrant();
 
     public Response createCode();
+    
+    public LoginFormsProvider setAuthenticationSession(AuthenticationSessionModel authenticationSession);
 
     public LoginFormsProvider setClientSessionCode(String accessCode);
 
diff --git a/services/src/main/java/org/keycloak/authentication/actiontoken/execactions/ExecuteActionsActionTokenHandler.java b/services/src/main/java/org/keycloak/authentication/actiontoken/execactions/ExecuteActionsActionTokenHandler.java
index a1c857f..43e4f90 100644
--- a/services/src/main/java/org/keycloak/authentication/actiontoken/execactions/ExecuteActionsActionTokenHandler.java
+++ b/services/src/main/java/org/keycloak/authentication/actiontoken/execactions/ExecuteActionsActionTokenHandler.java
@@ -80,6 +80,7 @@ public class ExecuteActionsActionTokenHandler extends AbstractActionTokenHander<
             String confirmUri = builder.build(realm.getName()).toString();
 
             return session.getProvider(LoginFormsProvider.class)
+                    .setAuthenticationSession(authSession)
                     .setSuccess(Messages.CONFIRM_EXECUTION_OF_ACTIONS)
                     .setAttribute(Constants.TEMPLATE_ATTR_ACTION_URI, confirmUri)
                     .setAttribute(Constants.TEMPLATE_ATTR_REQUIRED_ACTIONS, token.getRequiredActions())
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 c5dc897..7f9d58c 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
@@ -81,6 +81,7 @@ public class IdpVerifyAccountLinkActionTokenHandler extends AbstractActionTokenH
             String confirmUri = builder.build(realm.getName()).toString();
 
             return session.getProvider(LoginFormsProvider.class)
+                    .setAuthenticationSession(authSession)
                     .setSuccess(Messages.CONFIRM_ACCOUNT_LINKING, token.getIdentityProviderUsername(), token.getIdentityProviderAlias())
                     .setAttribute(Constants.TEMPLATE_ATTR_ACTION_URI, confirmUri)
                     .createInfoPage();
@@ -106,6 +107,7 @@ public class IdpVerifyAccountLinkActionTokenHandler extends AbstractActionTokenH
             }
 
             return session.getProvider(LoginFormsProvider.class)
+                    .setAuthenticationSession(authSession)
                     .setSuccess(Messages.IDENTITY_PROVIDER_LINK_SUCCESS, token.getIdentityProviderAlias(), token.getIdentityProviderUsername())
                     .setAttribute(Constants.SKIP_LINK, true)
                     .createInfoPage();
diff --git a/services/src/main/java/org/keycloak/authentication/actiontoken/resetcred/ResetCredentialsActionTokenHandler.java b/services/src/main/java/org/keycloak/authentication/actiontoken/resetcred/ResetCredentialsActionTokenHandler.java
index 0f08bd3..a17efef 100644
--- a/services/src/main/java/org/keycloak/authentication/actiontoken/resetcred/ResetCredentialsActionTokenHandler.java
+++ b/services/src/main/java/org/keycloak/authentication/actiontoken/resetcred/ResetCredentialsActionTokenHandler.java
@@ -85,7 +85,7 @@ public class ResetCredentialsActionTokenHandler extends AbstractActionTokenHande
 
                 UserModel linkingUser = AbstractIdpAuthenticator.getExistingUser(session, realm, authenticationSession);
                 if (!linkingUser.getId().equals(authenticationSession.getAuthenticatedUser().getId())) {
-                    return ErrorPage.error(session,
+                    return ErrorPage.error(session, authenticationSession,
                       Messages.IDENTITY_PROVIDER_DIFFERENT_USER_MESSAGE,
                       authenticationSession.getAuthenticatedUser().getUsername(),
                       linkingUser.getUsername()
diff --git a/services/src/main/java/org/keycloak/authentication/actiontoken/verifyemail/VerifyEmailActionTokenHandler.java b/services/src/main/java/org/keycloak/authentication/actiontoken/verifyemail/VerifyEmailActionTokenHandler.java
index b5d046e..98ad8b4 100644
--- a/services/src/main/java/org/keycloak/authentication/actiontoken/verifyemail/VerifyEmailActionTokenHandler.java
+++ b/services/src/main/java/org/keycloak/authentication/actiontoken/verifyemail/VerifyEmailActionTokenHandler.java
@@ -82,6 +82,7 @@ public class VerifyEmailActionTokenHandler extends AbstractActionTokenHander<Ver
             String confirmUri = builder.build(realm.getName()).toString();
 
             return session.getProvider(LoginFormsProvider.class)
+                    .setAuthenticationSession(authSession)
                     .setSuccess(Messages.CONFIRM_EMAIL_ADDRESS_VERIFICATION, user.getEmail())
                     .setAttribute(Constants.TEMPLATE_ATTR_ACTION_URI, confirmUri)
                     .createInfoPage();
@@ -99,6 +100,7 @@ public class VerifyEmailActionTokenHandler extends AbstractActionTokenHander<Ver
             asm.removeAuthenticationSession(tokenContext.getRealm(), authSession, true);
 
             return tokenContext.getSession().getProvider(LoginFormsProvider.class)
+                    .setAuthenticationSession(authSession)
                     .setSuccess(Messages.EMAIL_VERIFIED)
                     .createInfoPage();
         }
diff --git a/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java b/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java
index e212c92..c7a8ede 100755
--- a/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java
+++ b/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java
@@ -469,6 +469,7 @@ public class AuthenticationProcessor {
             String accessCode = generateAccessCode();
             URI action = getActionUrl(accessCode);
             LoginFormsProvider provider = getSession().getProvider(LoginFormsProvider.class)
+                    .setAuthenticationSession(getAuthenticationSession())
                     .setUser(getUser())
                     .setActionUri(action)
                     .setExecution(getExecution().getId())
@@ -609,25 +610,25 @@ public class AuthenticationProcessor {
             if (e.getError() == AuthenticationFlowError.INVALID_USER) {
                 ServicesLogger.LOGGER.failedAuthentication(e);
                 event.error(Errors.USER_NOT_FOUND);
-                return ErrorPage.error(session, Messages.INVALID_USER);
+                return ErrorPage.error(session, authenticationSession, Messages.INVALID_USER);
             } else if (e.getError() == AuthenticationFlowError.USER_DISABLED) {
                 ServicesLogger.LOGGER.failedAuthentication(e);
                 event.error(Errors.USER_DISABLED);
-                return ErrorPage.error(session, Messages.ACCOUNT_DISABLED);
+                return ErrorPage.error(session,authenticationSession, Messages.ACCOUNT_DISABLED);
             } else if (e.getError() == AuthenticationFlowError.USER_TEMPORARILY_DISABLED) {
                 ServicesLogger.LOGGER.failedAuthentication(e);
                 event.error(Errors.USER_TEMPORARILY_DISABLED);
-                return ErrorPage.error(session, Messages.INVALID_USER);
+                return ErrorPage.error(session,authenticationSession, Messages.INVALID_USER);
 
             } else if (e.getError() == AuthenticationFlowError.INVALID_CLIENT_SESSION) {
                 ServicesLogger.LOGGER.failedAuthentication(e);
                 event.error(Errors.INVALID_CODE);
-                return ErrorPage.error(session, Messages.INVALID_CODE);
+                return ErrorPage.error(session, authenticationSession, Messages.INVALID_CODE);
 
             } else if (e.getError() == AuthenticationFlowError.EXPIRED_CODE) {
                 ServicesLogger.LOGGER.failedAuthentication(e);
                 event.error(Errors.EXPIRED_CODE);
-                return ErrorPage.error(session, Messages.EXPIRED_CODE);
+                return ErrorPage.error(session, authenticationSession, Messages.EXPIRED_CODE);
 
             } else if (e.getError() == AuthenticationFlowError.FORK_FLOW) {
                 ForkFlowException reset = (ForkFlowException)e;
@@ -654,13 +655,13 @@ public class AuthenticationProcessor {
             } else {
                 ServicesLogger.LOGGER.failedAuthentication(e);
                 event.error(Errors.INVALID_USER_CREDENTIALS);
-                return ErrorPage.error(session, Messages.INVALID_USER);
+                return ErrorPage.error(session, authenticationSession, Messages.INVALID_USER);
             }
 
         } else {
             ServicesLogger.LOGGER.failedAuthentication(failure);
             event.error(Errors.INVALID_USER_CREDENTIALS);
-            return ErrorPage.error(session, Messages.UNEXPECTED_ERROR_HANDLING_REQUEST);
+            return ErrorPage.error(session, authenticationSession, Messages.UNEXPECTED_ERROR_HANDLING_REQUEST);
         }
 
     }
@@ -885,7 +886,7 @@ public class AuthenticationProcessor {
                 if (!authSession.getAuthenticatedUser().equals(userSession.getUser())) {
                     event.detail(Details.EXISTING_USER, userSession.getUser().getId());
                     event.error(Errors.DIFFERENT_USER_AUTHENTICATED);
-                    throw new ErrorPageException(session, Messages.DIFFERENT_USER_AUTHENTICATED, userSession.getUser().getUsername());
+                    throw new ErrorPageException(session, authSession, Messages.DIFFERENT_USER_AUTHENTICATED, userSession.getUser().getUsername());
                 }
             }
             userSession.setState(UserSessionModel.State.LOGGED_IN);
@@ -921,7 +922,8 @@ public class AuthenticationProcessor {
         if (!authenticatedUser.isEnabled()) throw new AuthenticationFlowException(AuthenticationFlowError.USER_DISABLED);
         if (realm.isBruteForceProtected() && !realm.isPermanentLockout()) {
             if (getBruteForceProtector().isTemporarilyDisabled(session, realm, authenticatedUser)) {
-                throw new AuthenticationFlowException(AuthenticationFlowError.USER_TEMPORARILY_DISABLED);
+                getEvent().error(Errors.RESET_CREDENTIAL_DISABLED);
+                ServicesLogger.LOGGER.passwordResetFailed(new AuthenticationFlowException(AuthenticationFlowError.USER_TEMPORARILY_DISABLED));
             }
         }
     }
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/browser/SpnegoAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/browser/SpnegoAuthenticator.java
index 6b72686..d90a0b9 100755
--- a/services/src/main/java/org/keycloak/authentication/authenticators/browser/SpnegoAuthenticator.java
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/browser/SpnegoAuthenticator.java
@@ -119,6 +119,7 @@ public class SpnegoAuthenticator extends AbstractUsernameFormAuthenticator imple
         }
         if (context.getExecution().isRequired()) {
             return context.getSession().getProvider(LoginFormsProvider.class)
+                    .setAuthenticationSession(context.getAuthenticationSession())
                     .setStatus(Response.Status.UNAUTHORIZED)
                     .setResponseHeader(HttpHeaders.WWW_AUTHENTICATE, negotiateHeader)
                     .setError(Messages.KERBEROS_NOT_ENABLED).createErrorPage();
diff --git a/services/src/main/java/org/keycloak/authentication/FormAuthenticationFlow.java b/services/src/main/java/org/keycloak/authentication/FormAuthenticationFlow.java
index 575677d..0a9fc07 100755
--- a/services/src/main/java/org/keycloak/authentication/FormAuthenticationFlow.java
+++ b/services/src/main/java/org/keycloak/authentication/FormAuthenticationFlow.java
@@ -269,6 +269,7 @@ public class FormAuthenticationFlow implements AuthenticationFlow {
         String code = processor.generateCode();
         URI actionUrl = getActionUrl(executionId, code);
         LoginFormsProvider form = processor.getSession().getProvider(LoginFormsProvider.class)
+                .setAuthenticationSession(processor.getAuthenticationSession())
                 .setActionUri(actionUrl)
                 .setExecution(executionId)
                 .setClientSessionCode(code)
diff --git a/services/src/main/java/org/keycloak/authentication/RequiredActionContextResult.java b/services/src/main/java/org/keycloak/authentication/RequiredActionContextResult.java
index 3ce9768..e87652a 100755
--- a/services/src/main/java/org/keycloak/authentication/RequiredActionContextResult.java
+++ b/services/src/main/java/org/keycloak/authentication/RequiredActionContextResult.java
@@ -166,6 +166,7 @@ public class RequiredActionContextResult implements RequiredActionContext {
         String accessCode = generateCode();
         URI action = getActionUrl(accessCode);
         LoginFormsProvider provider = getSession().getProvider(LoginFormsProvider.class)
+                .setAuthenticationSession(getAuthenticationSession())
                 .setUser(getUser())
                 .setActionUri(action)
                 .setExecution(getExecution())
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 c6f4b05..5d6e94a 100755
--- a/services/src/main/java/org/keycloak/broker/oidc/AbstractOAuth2IdentityProvider.java
+++ b/services/src/main/java/org/keycloak/broker/oidc/AbstractOAuth2IdentityProvider.java
@@ -39,7 +39,6 @@ import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.UserModel;
 import org.keycloak.models.UserSessionModel;
-import org.keycloak.representations.AccessToken;
 import org.keycloak.representations.AccessTokenResponse;
 import org.keycloak.protocol.oidc.OIDCLoginProtocol;
 import org.keycloak.services.ErrorPage;
@@ -404,7 +403,7 @@ public abstract class AbstractOAuth2IdentityProvider<C extends OAuth2IdentityPro
             }
             event.event(EventType.LOGIN);
             event.error(Errors.IDENTITY_PROVIDER_LOGIN_FAILURE);
-            return ErrorPage.error(session, Messages.IDENTITY_PROVIDER_UNEXPECTED_ERROR);
+            return ErrorPage.error(session, null, Messages.IDENTITY_PROVIDER_UNEXPECTED_ERROR);
         }
 
         public SimpleHttp generateTokenRequest(String authorizationCode) {
diff --git a/services/src/main/java/org/keycloak/broker/oidc/OIDCIdentityProvider.java b/services/src/main/java/org/keycloak/broker/oidc/OIDCIdentityProvider.java
index 7fceae8..5007971 100755
--- a/services/src/main/java/org/keycloak/broker/oidc/OIDCIdentityProvider.java
+++ b/services/src/main/java/org/keycloak/broker/oidc/OIDCIdentityProvider.java
@@ -111,14 +111,14 @@ public class OIDCIdentityProvider extends AbstractOAuth2IdentityProvider<OIDCIde
                 EventBuilder event = new EventBuilder(realm, session, clientConnection);
                 event.event(EventType.LOGOUT);
                 event.error(Errors.USER_SESSION_NOT_FOUND);
-                return ErrorPage.error(session, Messages.IDENTITY_PROVIDER_UNEXPECTED_ERROR);
+                return ErrorPage.error(session, null, Messages.IDENTITY_PROVIDER_UNEXPECTED_ERROR);
             }
             if (userSession.getState() != UserSessionModel.State.LOGGING_OUT) {
                 logger.error("usersession in different state");
                 EventBuilder event = new EventBuilder(realm, session, clientConnection);
                 event.event(EventType.LOGOUT);
                 event.error(Errors.USER_SESSION_NOT_FOUND);
-                return ErrorPage.error(session, Messages.SESSION_NOT_ACTIVE);
+                return ErrorPage.error(session, null, Messages.SESSION_NOT_ACTIVE);
             }
             return AuthenticationManager.finishBrowserLogout(session, realm, userSession, uriInfo, clientConnection, headers);
         }
diff --git a/services/src/main/java/org/keycloak/broker/saml/SAMLEndpoint.java b/services/src/main/java/org/keycloak/broker/saml/SAMLEndpoint.java
index ce5b77f..0b01bac 100755
--- a/services/src/main/java/org/keycloak/broker/saml/SAMLEndpoint.java
+++ b/services/src/main/java/org/keycloak/broker/saml/SAMLEndpoint.java
@@ -87,12 +87,10 @@ import java.util.List;
 import org.keycloak.rotation.HardcodedKeyLocator;
 import org.keycloak.rotation.KeyLocator;
 import org.keycloak.saml.processing.core.util.KeycloakKeySamlExtensionGenerator;
-import org.keycloak.saml.processing.core.util.XMLEncryptionUtil;
 import org.w3c.dom.Element;
 
 import java.util.*;
 import javax.xml.crypto.dsig.XMLSignature;
-import org.w3c.dom.Document;
 import org.w3c.dom.NodeList;
 
 /**
@@ -194,18 +192,18 @@ public class SAMLEndpoint {
             if (!checkSsl()) {
                 event.event(EventType.LOGIN);
                 event.error(Errors.SSL_REQUIRED);
-                return ErrorPage.error(session, Messages.HTTPS_REQUIRED);
+                return ErrorPage.error(session, null, Messages.HTTPS_REQUIRED);
             }
             if (!realm.isEnabled()) {
                 event.event(EventType.LOGIN_ERROR);
                 event.error(Errors.REALM_DISABLED);
-                return ErrorPage.error(session, Messages.REALM_NOT_ENABLED);
+                return ErrorPage.error(session, null, Messages.REALM_NOT_ENABLED);
             }
 
             if (samlRequest == null && samlResponse == null) {
                 event.event(EventType.LOGIN);
                 event.error(Errors.INVALID_REQUEST);
-                return ErrorPage.error(session, Messages.INVALID_REQUEST);
+                return ErrorPage.error(session, null, Messages.INVALID_REQUEST);
 
             }
             return null;
@@ -247,7 +245,7 @@ public class SAMLEndpoint {
                 event.event(EventType.IDENTITY_PROVIDER_RESPONSE);
                 event.detail(Details.REASON, "invalid_destination");
                 event.error(Errors.INVALID_SAML_RESPONSE);
-                return ErrorPage.error(session, Messages.INVALID_REQUEST);
+                return ErrorPage.error(session, null, Messages.INVALID_REQUEST);
             }
             if (config.isValidateSignature()) {
                 try {
@@ -256,7 +254,7 @@ public class SAMLEndpoint {
                     logger.error("validation failed", e);
                     event.event(EventType.IDENTITY_PROVIDER_RESPONSE);
                     event.error(Errors.INVALID_SIGNATURE);
-                    return ErrorPage.error(session, Messages.INVALID_REQUESTER);
+                    return ErrorPage.error(session, null, Messages.INVALID_REQUESTER);
                 }
             }
 
@@ -269,7 +267,7 @@ public class SAMLEndpoint {
             } else {
                 event.event(EventType.LOGIN);
                 event.error(Errors.INVALID_TOKEN);
-                return ErrorPage.error(session, Messages.INVALID_REQUEST);
+                return ErrorPage.error(session, null, Messages.INVALID_REQUEST);
             }
         }
 
@@ -342,6 +340,7 @@ public class SAMLEndpoint {
         private String getEntityId(UriInfo uriInfo, RealmModel realm) {
             return UriBuilder.fromUri(uriInfo.getBaseUri()).path("realms").path(realm.getName()).build().toString();
         }
+        
         protected Response handleLoginResponse(String samlResponse, SAMLDocumentHolder holder, ResponseType responseType, String relayState, String clientId) {
 
             try {
@@ -360,7 +359,7 @@ public class SAMLEndpoint {
                     logger.error("The assertion is not encrypted, which is required.");
                     event.event(EventType.IDENTITY_PROVIDER_RESPONSE);
                     event.error(Errors.INVALID_SAML_RESPONSE);
-                    return ErrorPage.error(session, Messages.INVALID_REQUESTER);
+                    return ErrorPage.error(session, null, Messages.INVALID_REQUESTER);
                 }
 
                 Element assertionElement;
@@ -380,7 +379,7 @@ public class SAMLEndpoint {
                     logger.error("validation failed");
                     event.event(EventType.IDENTITY_PROVIDER_RESPONSE);
                     event.error(Errors.INVALID_SIGNATURE);
-                    return ErrorPage.error(session, Messages.INVALID_REQUESTER);
+                    return ErrorPage.error(session, null, Messages.INVALID_REQUESTER);
                 }
 
                 AssertionType assertion = responseType.getAssertions().get(0).getAssertion();
@@ -464,7 +463,7 @@ public class SAMLEndpoint {
                 event.event(EventType.IDENTITY_PROVIDER_RESPONSE);
                 event.detail(Details.REASON, "invalid_destination");
                 event.error(Errors.INVALID_SAML_RESPONSE);
-                return ErrorPage.error(session, Messages.INVALID_FEDERATED_IDENTITY_ACTION);
+                return ErrorPage.error(session, null, Messages.INVALID_FEDERATED_IDENTITY_ACTION);
             }
             if (config.isValidateSignature()) {
                 try {
@@ -473,7 +472,7 @@ public class SAMLEndpoint {
                     logger.error("validation failed", e);
                     event.event(EventType.IDENTITY_PROVIDER_RESPONSE);
                     event.error(Errors.INVALID_SIGNATURE);
-                    return ErrorPage.error(session, Messages.INVALID_FEDERATED_IDENTITY_ACTION);
+                    return ErrorPage.error(session, null, Messages.INVALID_FEDERATED_IDENTITY_ACTION);
                 }
             }
             if (statusResponse instanceof ResponseType) {
@@ -492,20 +491,20 @@ public class SAMLEndpoint {
                 logger.error("no valid user session");
                 event.event(EventType.LOGOUT);
                 event.error(Errors.USER_SESSION_NOT_FOUND);
-                return ErrorPage.error(session, Messages.IDENTITY_PROVIDER_UNEXPECTED_ERROR);
+                return ErrorPage.error(session, null, Messages.IDENTITY_PROVIDER_UNEXPECTED_ERROR);
             }
             UserSessionModel userSession = session.sessions().getUserSession(realm, relayState);
             if (userSession == null) {
                 logger.error("no valid user session");
                 event.event(EventType.LOGOUT);
                 event.error(Errors.USER_SESSION_NOT_FOUND);
-                return ErrorPage.error(session, Messages.IDENTITY_PROVIDER_UNEXPECTED_ERROR);
+                return ErrorPage.error(session, null, Messages.IDENTITY_PROVIDER_UNEXPECTED_ERROR);
             }
             if (userSession.getState() != UserSessionModel.State.LOGGING_OUT) {
                 logger.error("usersession in different state");
                 event.event(EventType.LOGOUT);
                 event.error(Errors.USER_SESSION_NOT_FOUND);
-                return ErrorPage.error(session, Messages.SESSION_NOT_ACTIVE);
+                return ErrorPage.error(session, null, Messages.SESSION_NOT_ACTIVE);
             }
             return AuthenticationManager.finishBrowserLogout(session, realm, userSession, uriInfo, clientConnection, headers);
         }
diff --git a/services/src/main/java/org/keycloak/email/freemarker/FreeMarkerEmailTemplateProvider.java b/services/src/main/java/org/keycloak/email/freemarker/FreeMarkerEmailTemplateProvider.java
index 09a3789..a60634f 100755
--- a/services/src/main/java/org/keycloak/email/freemarker/FreeMarkerEmailTemplateProvider.java
+++ b/services/src/main/java/org/keycloak/email/freemarker/FreeMarkerEmailTemplateProvider.java
@@ -35,6 +35,7 @@ import org.keycloak.theme.Theme;
 import org.keycloak.theme.ThemeProvider;
 import org.keycloak.theme.beans.MessageFormatterMethod;
 
+import java.io.IOException;
 import java.text.MessageFormat;
 import java.util.Arrays;
 import java.util.Collections;
@@ -150,7 +151,6 @@ public class FreeMarkerEmailTemplateProvider implements EmailTemplateProvider {
         attributes.put("realmName", getRealmName());
 
         send("executeActionsSubject", "executeActions.ftl", attributes);
-
     }
 
     @Override
@@ -171,8 +171,7 @@ public class FreeMarkerEmailTemplateProvider implements EmailTemplateProvider {
 
     protected EmailTemplate processTemplate(String subjectKey, List<Object> subjectAttributes, String template, Map<String, Object> attributes) throws EmailException {
         try {
-            ThemeProvider themeProvider = session.getProvider(ThemeProvider.class, "extending");
-            Theme theme = themeProvider.getTheme(realm.getEmailTheme(), Theme.Type.EMAIL);
+            Theme theme = getTheme();
             Locale locale = session.getContext().resolveLocale(user);
             attributes.put("locale", locale);
             Properties rb = theme.getMessages(locale);
@@ -198,10 +197,18 @@ public class FreeMarkerEmailTemplateProvider implements EmailTemplateProvider {
             throw new EmailException("Failed to template email", e);
         }
     }
+
+    protected Theme getTheme() throws IOException {
+        ThemeProvider themeProvider = session.getProvider(ThemeProvider.class, "extending");
+        return themeProvider.getTheme(realm.getEmailTheme(), Theme.Type.EMAIL);
+    }
+    
     protected void send(String subjectKey, List<Object> subjectAttributes, String template, Map<String, Object> attributes) throws EmailException {
         try {
             EmailTemplate email = processTemplate(subjectKey, subjectAttributes, template, attributes);
             send(email.getSubject(), email.getTextBody(), email.getHtmlBody());
+        } catch (EmailException e){
+            throw e;
         } catch (Exception e) {
             throw new EmailException("Failed to template email", e);
         }
diff --git a/services/src/main/java/org/keycloak/forms/account/freemarker/FreeMarkerAccountProvider.java b/services/src/main/java/org/keycloak/forms/account/freemarker/FreeMarkerAccountProvider.java
index 6241c85..855b388 100755
--- a/services/src/main/java/org/keycloak/forms/account/freemarker/FreeMarkerAccountProvider.java
+++ b/services/src/main/java/org/keycloak/forms/account/freemarker/FreeMarkerAccountProvider.java
@@ -71,26 +71,26 @@ public class FreeMarkerAccountProvider implements AccountProvider {
 
     private static final Logger logger = Logger.getLogger(FreeMarkerAccountProvider.class);
 
-    private UserModel user;
-    private MultivaluedMap<String, String> profileFormData;
-    private Response.Status status = Response.Status.OK;
-    private RealmModel realm;
-    private String[] referrer;
-    private List<Event> events;
-    private String stateChecker;
-    private List<UserSessionModel> sessions;
-    private boolean identityProviderEnabled;
-    private boolean eventsEnabled;
-    private boolean passwordUpdateSupported;
-    private boolean passwordSet;
-    private KeycloakSession session;
-    private FreeMarkerUtil freeMarker;
-    private HttpHeaders headers;
-
-    private UriInfo uriInfo;
-
-    private List<FormMessage> messages = null;
-    private MessageType messageType = MessageType.ERROR;
+    protected UserModel user;
+    protected MultivaluedMap<String, String> profileFormData;
+    protected Response.Status status = Response.Status.OK;
+    protected RealmModel realm;
+    protected String[] referrer;
+    protected List<Event> events;
+    protected String stateChecker;
+    protected List<UserSessionModel> sessions;
+    protected boolean identityProviderEnabled;
+    protected boolean eventsEnabled;
+    protected boolean passwordUpdateSupported;
+    protected boolean passwordSet;
+    protected KeycloakSession session;
+    protected FreeMarkerUtil freeMarker;
+    protected HttpHeaders headers;
+
+    protected UriInfo uriInfo;
+
+    protected List<FormMessage> messages = null;
+    protected MessageType messageType = MessageType.ERROR;
 
     public FreeMarkerAccountProvider(KeycloakSession session, FreeMarkerUtil freeMarker) {
         this.session = session;
@@ -112,30 +112,16 @@ public class FreeMarkerAccountProvider implements AccountProvider {
     public Response createResponse(AccountPages page) {
         Map<String, Object> attributes = new HashMap<String, Object>();
 
-        ThemeProvider themeProvider = session.getProvider(ThemeProvider.class, "extending");
         Theme theme;
         try {
-            theme = themeProvider.getTheme(realm.getAccountTheme(), Theme.Type.ACCOUNT);
+            theme = getTheme();
         } catch (IOException e) {
             logger.error("Failed to create theme", e);
             return Response.serverError().build();
         }
 
-        try {
-            attributes.put("properties", theme.getProperties());
-        } catch (IOException e) {
-            logger.warn("Failed to load properties", e);
-        }
-
         Locale locale = session.getContext().resolveLocale(user);
-        Properties messagesBundle;
-        try {
-            messagesBundle = theme.getMessages(locale);
-            attributes.put("msg", new MessageFormatterMethod(locale, messagesBundle));
-        } catch (IOException e) {
-            logger.warn("Failed to load messages", e);
-            messagesBundle = new Properties();
-        }
+        Properties messagesBundle = handleThemeResources(theme, locale, attributes);
 
         URI baseUri = uriInfo.getBaseUri();
         UriBuilder baseUriBuilder = uriInfo.getBaseUriBuilder();
@@ -148,19 +134,7 @@ public class FreeMarkerAccountProvider implements AccountProvider {
             attributes.put("stateChecker", stateChecker);
         }
 
-        MessagesPerFieldBean messagesPerField = new MessagesPerFieldBean();
-        if (messages != null) {
-            MessageBean wholeMessage = new MessageBean(null, messageType);
-            for (FormMessage message : this.messages) {
-                String formattedMessageText = formatMessage(message, messagesBundle, locale);
-                if (formattedMessageText != null) {
-                    wholeMessage.appendSummaryLine(formattedMessageText);
-                    messagesPerField.addMessage(message.getField(), formattedMessageText, messageType);
-                }
-            }
-            attributes.put("message", wholeMessage);
-        }
-        attributes.put("messagesPerField", messagesPerField);
+        handleMessages(locale, messagesBundle, attributes);
 
         if (referrer != null) {
             attributes.put("referrer", new ReferrerBean(referrer));
@@ -173,12 +147,7 @@ public class FreeMarkerAccountProvider implements AccountProvider {
         attributes.put("url", new UrlBean(realm, theme, baseUri, baseQueryUri, uriInfo.getRequestUri(), stateChecker));
 
         if (realm.isInternationalizationEnabled()) {
-            UriBuilder b;
-            switch (page) {
-                default:
-                    b = UriBuilder.fromUri(baseQueryUri).path(uriInfo.getPath());
-                    break;
-            }
+            UriBuilder b = UriBuilder.fromUri(baseQueryUri).path(uriInfo.getPath());
             attributes.put("locale", new LocaleBean(realm, locale, b, messagesBundle));
         }
 
@@ -204,8 +173,84 @@ public class FreeMarkerAccountProvider implements AccountProvider {
                 break;
             case PASSWORD:
                 attributes.put("password", new PasswordBean(passwordSet));
+                break;
+            default:
         }
 
+        return processTemplate(theme, page, attributes, locale);
+    }
+
+    /**
+     * Get Theme used for page rendering.
+     * 
+     * @return theme for page rendering, never null
+     * @throws IOException in case of Theme loading problem
+     */
+    protected Theme getTheme() throws IOException {
+        ThemeProvider themeProvider = session.getProvider(ThemeProvider.class, "extending");
+        return themeProvider.getTheme(realm.getAccountTheme(), Theme.Type.ACCOUNT);
+    }
+
+    /**
+     * Load message bundle and place it into <code>msg</code> template attribute. Also load Theme properties and place them into <code>properties</code> template attribute.
+     * 
+     * @param theme actual Theme to load bundle from
+     * @param locale to load bundle for
+     * @param attributes template attributes to add resources to
+     * @return message bundle for other use
+     */
+    protected Properties handleThemeResources(Theme theme, Locale locale, Map<String, Object> attributes) {
+        Properties messagesBundle;
+        try {
+            messagesBundle = theme.getMessages(locale);
+            attributes.put("msg", new MessageFormatterMethod(locale, messagesBundle));
+        } catch (IOException e) {
+            logger.warn("Failed to load messages", e);
+            messagesBundle = new Properties();
+        }
+        try {
+            attributes.put("properties", theme.getProperties());
+        } catch (IOException e) {
+            logger.warn("Failed to load properties", e);
+        }
+        return messagesBundle;
+    }
+
+    /**
+     * Handle messages to be shown on the page - set them to template attributes
+     * 
+     * @param locale to be used for message text loading
+     * @param messagesBundle to be used for message text loading
+     * @param attributes template attributes to messages related info to
+     * @see #messageType
+     * @see #messages
+     */
+    protected void handleMessages(Locale locale, Properties messagesBundle, Map<String, Object> attributes) {
+        MessagesPerFieldBean messagesPerField = new MessagesPerFieldBean();
+        if (messages != null) {
+            MessageBean wholeMessage = new MessageBean(null, messageType);
+            for (FormMessage message : this.messages) {
+                String formattedMessageText = formatMessage(message, messagesBundle, locale);
+                if (formattedMessageText != null) {
+                    wholeMessage.appendSummaryLine(formattedMessageText);
+                    messagesPerField.addMessage(message.getField(), formattedMessageText, messageType);
+                }
+            }
+            attributes.put("message", wholeMessage);
+        }
+        attributes.put("messagesPerField", messagesPerField);
+    }
+
+    /**
+     * Process FreeMarker template and prepare Response. Some fields are used for rendering also.
+     * 
+     * @param theme to be used (provided by <code>getTheme()</code>)
+     * @param page to be rendered
+     * @param attributes pushed to the template
+     * @param locale to be used
+     * @return Response object to be returned to the browser, never null
+     */
+    protected Response processTemplate(Theme theme, AccountPages page, Map<String, Object> attributes, Locale locale) {
         try {
             String result = freeMarker.processTemplate(attributes, Templates.getTemplate(page), theme);
             Response.ResponseBuilder builder = Response.status(status).type(MediaType.TEXT_HTML_UTF_8_TYPE).language(locale).entity(result);
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 8ec6a5b..e4cb41d 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
@@ -39,6 +39,7 @@ 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;
@@ -68,42 +69,52 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
 
     private static final Logger logger = Logger.getLogger(FreeMarkerLoginFormsProvider.class);
 
-    private String accessCode;
-    private Response.Status status;
-    private List<RoleModel> realmRolesRequested;
-    private MultivaluedMap<String, RoleModel> resourceRolesRequested;
-    private List<ProtocolMapperModel> protocolMappersRequested;
-    private Map<String, String> httpResponseHeaders = new HashMap<String, String>();
-    private String accessRequestMessage;
-    private URI actionUri;
-    private String execution;
+    protected String accessCode;
+    protected Response.Status status;
+    protected List<RoleModel> realmRolesRequested;
+    protected MultivaluedMap<String, RoleModel> resourceRolesRequested;
+    protected List<ProtocolMapperModel> protocolMappersRequested;
+    protected Map<String, String> httpResponseHeaders = new HashMap<String, String>();
+    protected String accessRequestMessage;
+    protected URI actionUri;
+    protected String execution;
 
-    private List<FormMessage> messages = null;
-    private MessageType messageType = MessageType.ERROR;
+    protected List<FormMessage> messages = null;
+    protected MessageType messageType = MessageType.ERROR;
 
-    private MultivaluedMap<String, String> formData;
+    protected MultivaluedMap<String, String> formData;
 
-    private KeycloakSession session;
-    private FreeMarkerUtil freeMarker;
+    protected KeycloakSession session;
+    /** authenticationSession can be null for some renderings, mainly error pages */
+    protected AuthenticationSessionModel authenticationSession;
+    protected RealmModel realm;
+    protected ClientModel client;
+    protected UriInfo uriInfo;
 
-    private UserModel user;
+    protected FreeMarkerUtil freeMarker;
 
-    private final Map<String, Object> attributes = new HashMap<String, Object>();
+    protected UserModel user;
+
+    protected final Map<String, Object> attributes = new HashMap<String, Object>();
 
     public FreeMarkerLoginFormsProvider(KeycloakSession session, FreeMarkerUtil freeMarker) {
         this.session = session;
         this.freeMarker = freeMarker;
         this.attributes.put("scripts", new LinkedList<String>());
+        this.realm = session.getContext().getRealm();
+        this.client = session.getContext().getClient();
+        this.uriInfo = session.getContext().getUri();
     }
 
+    @SuppressWarnings("unchecked")
     @Override
     public void addScript(String scriptUrl) {
-        List<String> scripts = (List<String>)this.attributes.get("scripts");
+        List<String> scripts = (List<String>) this.attributes.get("scripts");
         scripts.add(scriptUrl);
     }
 
+    @Override
     public Response createResponse(UserModel.RequiredAction action) {
-        RealmModel realm = session.getContext().getRealm();
 
         String actionMessage;
         LoginFormsPages page;
@@ -139,110 +150,29 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
         return createResponse(page);
     }
 
-    private Response createResponse(LoginFormsPages page) {
-        RealmModel realm = session.getContext().getRealm();
-        ClientModel client = session.getContext().getClient();
-        UriInfo uriInfo = session.getContext().getUri();
-
-        String requestURI = uriInfo.getBaseUri().getPath();
-        UriBuilder uriBuilder = UriBuilder.fromUri(requestURI);
-        if (page == LoginFormsPages.OAUTH_GRANT) {
-            // for some reason Resteasy 2.3.7 doesn't like query params and form params with the same name and will null out the code form param
-            uriBuilder.replaceQuery(null);
-        }
+    @SuppressWarnings("incomplete-switch")
+    protected Response createResponse(LoginFormsPages page) {
 
-        if (client != null) {
-            uriBuilder.queryParam(Constants.CLIENT_ID, client.getClientId());
-        }
-
-        URI baseUri = uriBuilder.build();
-
-        if (accessCode != null) {
-            uriBuilder.queryParam(OAuth2Constants.CODE, accessCode);
+        if (status == null) {
+            status = Response.Status.OK;
         }
 
-        URI baseUriWithCodeAndClientId = uriBuilder.build();
-
-        ThemeProvider themeProvider = session.getProvider(ThemeProvider.class, "extending");
         Theme theme;
         try {
-            theme = themeProvider.getTheme(realm.getLoginTheme(), Theme.Type.LOGIN);
+            theme = getTheme();
         } catch (IOException e) {
             logger.error("Failed to create theme", e);
             return Response.serverError().build();
         }
 
-        try {
-            attributes.put("properties", theme.getProperties());
-        } catch (IOException e) {
-            logger.warn("Failed to load properties", e);
-        }
-
-        Properties messagesBundle;
         Locale locale = session.getContext().resolveLocale(user);
-        try {
-            messagesBundle = theme.getMessages(locale);
-            attributes.put("msg", new MessageFormatterMethod(locale, messagesBundle));
-        } catch (IOException e) {
-            logger.warn("Failed to load messages", e);
-            messagesBundle = new Properties();
-        }
+        Properties messagesBundle = handleThemeResources(theme, locale);
 
-        MessagesPerFieldBean messagesPerField = new MessagesPerFieldBean();
-        if (messages != null) {
-            MessageBean wholeMessage = new MessageBean(null, messageType);
-            for (FormMessage message : this.messages) {
-                String formattedMessageText = formatMessage(message, messagesBundle, locale);
-                if (formattedMessageText != null) {
-                    wholeMessage.appendSummaryLine(formattedMessageText);
-                    messagesPerField.addMessage(message.getField(), formattedMessageText, messageType);
-                }
-            }
-            attributes.put("message", wholeMessage);
-        } else {
-            attributes.put("message", null);
-        }
-        attributes.put("messagesPerField", messagesPerField);
-
-        attributes.put("requiredActionUrl", new RequiredActionUrlFormatterMethod(realm, baseUri));
-        if (realm != null && user != null && session != null) {
-            attributes.put("authenticatorConfigured", new AuthenticatorConfiguredMethod(realm, user, session));
-        }
-
-        if (realm != null) {
-            attributes.put("realm", new RealmBean(realm));
-
-            List<IdentityProviderModel> identityProviders = realm.getIdentityProviders();
-            identityProviders = LoginFormsUtil.filterIdentityProviders(identityProviders, session, realm, attributes, formData);
-            attributes.put("social", new IdentityProviderBean(realm, session, identityProviders, baseUriWithCodeAndClientId));
-
-            attributes.put("url", new UrlBean(realm, theme, baseUri, this.actionUri));
-
-            if (realm.isInternationalizationEnabled()) {
-                UriBuilder b;
-                switch (page) {
-                    case LOGIN:
-                        b = UriBuilder.fromUri(Urls.realmLoginPage(baseUri, realm.getName()));
-                        break;
-                    case REGISTER:
-                        b = UriBuilder.fromUri(Urls.realmRegisterPage(baseUri, realm.getName()));
-                        break;
-                    default:
-                        b = UriBuilder.fromUri(baseUri).path(uriInfo.getPath());
-                        break;
-                }
-
-                if (execution != null) {
-                    b.queryParam(Constants.EXECUTION, execution);
-                }
+        handleMessages(locale, messagesBundle);
 
-                attributes.put("locale", new LocaleBean(realm, locale, b, messagesBundle));
-            }
-        }
-
-        if (client != null) {
-            attributes.put("client", new ClientBean(client, baseUri));
-        }
+        // for some reason Resteasy 2.3.7 doesn't like query params and form params with the same name and will null out the code form param
+        UriBuilder uriBuilder = prepareBaseUriBuilder(page == LoginFormsPages.OAUTH_GRANT);
+        createCommonAttributes(theme, locale, messagesBundle, uriBuilder, page);
 
         attributes.put("login", new LoginBean(formData));
 
@@ -267,7 +197,8 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
                 attributes.put("register", new RegisterBean(formData));
                 break;
             case OAUTH_GRANT:
-                attributes.put("oauth", new OAuthGrantBean(accessCode, client, realmRolesRequested, resourceRolesRequested, protocolMappersRequested, this.accessRequestMessage));
+                attributes.put("oauth",
+                        new OAuthGrantBean(accessCode, client, realmRolesRequested, resourceRolesRequested, protocolMappersRequested, this.accessRequestMessage));
                 attributes.put("advancedMsg", new AdvancedMessageFormatterMethod(locale, messagesBundle));
                 break;
             case CODE:
@@ -279,62 +210,74 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
             status = Response.Status.OK;
         }
 
-        try {
-            String result = freeMarker.processTemplate(attributes, Templates.getTemplate(page), theme);
-            Response.ResponseBuilder builder = Response.status(status).type(MediaType.TEXT_HTML_UTF_8).entity(result);
-            BrowserSecurityHeaderSetup.headers(builder, realm);
-            for (Map.Entry<String, String> entry : httpResponseHeaders.entrySet()) {
-                builder.header(entry.getKey(), entry.getValue());
-            }
-            return builder.build();
-        } catch (FreeMarkerException e) {
-            logger.error("Failed to process template", e);
-            return Response.serverError().build();
-        }
+        return processTemplate(theme, Templates.getTemplate(page), locale);
     }
 
     @Override
     public Response createForm(String form) {
 
-        RealmModel realm = session.getContext().getRealm();
-        ClientModel client = session.getContext().getClient();
-        UriInfo uriInfo = session.getContext().getUri();
-
-        String requestURI = uriInfo.getBaseUri().getPath();
-        UriBuilder uriBuilder = UriBuilder.fromUri(requestURI);
-
-        if (client != null) {
-            uriBuilder.queryParam(Constants.CLIENT_ID, client.getClientId());
-        }
-
-        URI baseUri = uriBuilder.build();
-
-        if (accessCode != null) {
-            uriBuilder.queryParam(OAuth2Constants.CODE, accessCode);
+        if (status == null) {
+            status = Response.Status.OK;
         }
 
-        URI baseUriWithCode = uriBuilder.build();
-
-        ThemeProvider themeProvider = session.getProvider(ThemeProvider.class, "extending");
         Theme theme;
         try {
-            theme = themeProvider.getTheme(realm.getLoginTheme(), Theme.Type.LOGIN);
+            theme = getTheme();
         } catch (IOException e) {
             logger.error("Failed to create theme", e);
             return Response.serverError().build();
         }
 
-        try {
-            attributes.put("properties", theme.getProperties());
-        } catch (IOException e) {
-            logger.warn("Failed to load properties", e);
+        Locale locale = session.getContext().resolveLocale(user);
+        Properties messagesBundle = handleThemeResources(theme, locale);
+
+        handleMessages(locale, messagesBundle);
+
+        UriBuilder uriBuilder = prepareBaseUriBuilder(false);
+        createCommonAttributes(theme, locale, messagesBundle, uriBuilder, null);
+
+        return processTemplate(theme, form, locale);
+    }
+
+    /**
+     * Prepare base uri builder for later use
+     * 
+     * @param resetRequestUriParams - for some reason Resteasy 2.3.7 doesn't like query params and form params with the same name and will null out the code form param, so we have to reset them for some pages
+     * @return base uri builder  
+     */
+    protected UriBuilder prepareBaseUriBuilder(boolean resetRequestUriParams) {
+        String requestURI = uriInfo.getBaseUri().getPath();
+        UriBuilder uriBuilder = UriBuilder.fromUri(requestURI);
+        if (resetRequestUriParams) {
+            uriBuilder.replaceQuery(null);
         }
+
         if (client != null) {
-            attributes.put("client", new ClientBean(client, baseUri));
+            uriBuilder.queryParam(Constants.CLIENT_ID, client.getClientId());
         }
+        return uriBuilder;
+    }
 
+    /**
+     * Get Theme used for page rendering.
+     * 
+     * @return theme for page rendering, never null
+     * @throws IOException in case of Theme loading problem
+     */
+    protected Theme getTheme() throws IOException {
+        ThemeProvider themeProvider = session.getProvider(ThemeProvider.class, "extending");
+        return themeProvider.getTheme(realm.getLoginTheme(), Theme.Type.LOGIN);
+    }
+
+    /**
+     * Load message bundle and place it into <code>msg</code> template attribute. Also load Theme properties and place them into <code>properties</code> template attribute.
+     * 
+     * @param theme actual Theme to load bundle from
+     * @param locale to load bundle for
+     * @return message bundle for other use
+     */
+    protected Properties handleThemeResources(Theme theme, Locale locale) {
         Properties messagesBundle;
-        Locale locale = session.getContext().resolveLocale(user);
         try {
             messagesBundle = theme.getMessages(locale);
             attributes.put("msg", new MessageFormatterMethod(locale, messagesBundle));
@@ -343,6 +286,24 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
             messagesBundle = new Properties();
         }
 
+        try {
+            attributes.put("properties", theme.getProperties());
+        } catch (IOException e) {
+            logger.warn("Failed to load properties", e);
+        }
+
+        return messagesBundle;
+    }
+
+    /**
+     * Handle messages to be shown on the page - set them to template attributes
+     * 
+     * @param locale to be used for message text loading
+     * @param messagesBundle to be used for message text loading
+     * @see #messageType
+     * @see #messages
+     */
+    protected void handleMessages(Locale locale, Properties messagesBundle) {
         MessagesPerFieldBean messagesPerField = new MessagesPerFieldBean();
         if (messages != null) {
             MessageBean wholeMessage = new MessageBean(null, messageType);
@@ -354,11 +315,31 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
                 }
             }
             attributes.put("message", wholeMessage);
+        } else {
+            attributes.put("message", null);
         }
         attributes.put("messagesPerField", messagesPerField);
+    }
+    
+    /**
+     * Create common attributes used in all templates.
+     * 
+     * @param theme actual Theme used (provided by <code>getTheme()</code>) 
+     * @param locale actual locale
+     * @param messagesBundle actual message bundle (provided by <code>handleThemeResources()</code>)
+     * @param baseUriBuilder actual base uri builder (provided by <code>prepareBaseUriBuilder()</code>)
+     * @param page in case if common page is rendered, is null if called from <code>createForm()</code>
+     * 
+     */
+    protected void createCommonAttributes(Theme theme, Locale locale, Properties messagesBundle, UriBuilder baseUriBuilder, LoginFormsPages page) {
+        URI baseUri = baseUriBuilder.build();
+        if (accessCode != null) {
+            baseUriBuilder.queryParam(OAuth2Constants.CODE, accessCode);
+        }
+        URI baseUriWithCodeAndClientId = baseUriBuilder.build();
 
-        if (status == null) {
-            status = Response.Status.OK;
+        if (client != null) {
+            attributes.put("client", new ClientBean(client, baseUri));
         }
 
         if (realm != null) {
@@ -366,14 +347,29 @@ 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));
             attributes.put("requiredActionUrl", new RequiredActionUrlFormatterMethod(realm, baseUri));
 
             if (realm.isInternationalizationEnabled()) {
-                UriBuilder b = UriBuilder.fromUri(baseUri)
-                        .path(uriInfo.getPath());
+                UriBuilder b;
+                if (page != null) {
+                    switch (page) {
+                        case LOGIN:
+                            b = UriBuilder.fromUri(Urls.realmLoginPage(baseUri, realm.getName()));
+                            break;
+                        case REGISTER:
+                            b = UriBuilder.fromUri(Urls.realmRegisterPage(baseUri, realm.getName()));
+                            break;
+                        default:
+                            b = UriBuilder.fromUri(baseUri).path(uriInfo.getPath());
+                            break;
+                    }
+                } else {
+                    b = UriBuilder.fromUri(baseUri)
+                            .path(uriInfo.getPath());
+                }
 
                 if (execution != null) {
                     b.queryParam(Constants.EXECUTION, execution);
@@ -385,8 +381,19 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
         if (realm != null && user != null && session != null) {
             attributes.put("authenticatorConfigured", new AuthenticatorConfiguredMethod(realm, user, session));
         }
+    }
+
+    /**
+     * Process FreeMarker template and prepare Response. Some fields are used for rendering also.
+     * 
+     * @param theme to be used (provided by <code>getTheme()</code>)
+     * @param templateName name of the template to be rendered
+     * @param locale to be used
+     * @return Response object to be returned to the browser, never null
+     */
+    protected Response processTemplate(Theme theme, String templateName, Locale locale) {
         try {
-            String result = freeMarker.processTemplate(attributes, form, theme);
+            String result = freeMarker.processTemplate(attributes, templateName, theme);
             Response.ResponseBuilder builder = Response.status(status).type(MediaType.TEXT_HTML_UTF_8_TYPE).language(locale).entity(result);
             BrowserSecurityHeaderSetup.headers(builder, realm);
             for (Map.Entry<String, String> entry : httpResponseHeaders.entrySet()) {
@@ -434,7 +441,6 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
         return createResponse(LoginFormsPages.LOGIN_UPDATE_PROFILE);
     }
 
-
     @Override
     public Response createIdpLinkConfirmLinkPage() {
         return createResponse(LoginFormsPages.LOGIN_IDP_LINK_CONFIRM);
@@ -504,7 +510,8 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
 
     @Override
     public LoginFormsProvider setErrors(List<FormMessage> messages) {
-        if (messages == null) return this;
+        if (messages == null)
+            return this;
         this.messageType = MessageType.ERROR;
         this.messages = new ArrayList<>(messages);
         return this;
@@ -553,6 +560,12 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
     }
 
     @Override
+    public LoginFormsProvider setAuthenticationSession(AuthenticationSessionModel authenticationSession) {
+        this.authenticationSession = authenticationSession;
+        return this;
+    }
+
+    @Override
     public FreeMarkerLoginFormsProvider setUser(UserModel user) {
         this.user = user;
         return this;
@@ -571,7 +584,8 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
     }
 
     @Override
-    public LoginFormsProvider setAccessRequest(List<RoleModel> realmRolesRequested, MultivaluedMap<String, RoleModel> resourceRolesRequested, List<ProtocolMapperModel> protocolMappersRequested) {
+    public LoginFormsProvider setAccessRequest(List<RoleModel> realmRolesRequested, MultivaluedMap<String, RoleModel> resourceRolesRequested,
+            List<ProtocolMapperModel> protocolMappersRequested) {
         this.realmRolesRequested = realmRolesRequested;
         this.resourceRolesRequested = resourceRolesRequested;
         this.protocolMappersRequested = protocolMappersRequested;
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/AuthorizationEndpoint.java b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/AuthorizationEndpoint.java
index f00c892..5fa46a0 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/AuthorizationEndpoint.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/AuthorizationEndpoint.java
@@ -153,7 +153,7 @@ public class AuthorizationEndpoint extends AuthorizationEndpointBase {
         action = Action.REGISTER;
 
         if (!realm.isRegistrationAllowed()) {
-            throw new ErrorPageException(session, Messages.REGISTRATION_NOT_ALLOWED);
+            throw new ErrorPageException(session, authenticationSession, Messages.REGISTRATION_NOT_ALLOWED);
         }
 
         return this;
@@ -164,7 +164,7 @@ public class AuthorizationEndpoint extends AuthorizationEndpointBase {
         action = Action.FORGOT_CREDENTIALS;
 
         if (!realm.isResetPasswordAllowed()) {
-            throw new ErrorPageException(session, Messages.RESET_CREDENTIAL_NOT_ALLOWED);
+            throw new ErrorPageException(session, authenticationSession, Messages.RESET_CREDENTIAL_NOT_ALLOWED);
         }
 
         return this;
@@ -173,7 +173,7 @@ public class AuthorizationEndpoint extends AuthorizationEndpointBase {
     private void checkClient(String clientId) {
         if (clientId == null) {
             event.error(Errors.INVALID_REQUEST);
-            throw new ErrorPageException(session, Messages.MISSING_PARAMETER, OIDCLoginProtocol.CLIENT_ID_PARAM);
+            throw new ErrorPageException(session, authenticationSession, Messages.MISSING_PARAMETER, OIDCLoginProtocol.CLIENT_ID_PARAM);
         }
 
         event.client(clientId);
@@ -181,17 +181,17 @@ public class AuthorizationEndpoint extends AuthorizationEndpointBase {
         client = realm.getClientByClientId(clientId);
         if (client == null) {
             event.error(Errors.CLIENT_NOT_FOUND);
-            throw new ErrorPageException(session, Messages.CLIENT_NOT_FOUND);
+            throw new ErrorPageException(session, authenticationSession, Messages.CLIENT_NOT_FOUND);
         }
 
         if (!client.isEnabled()) {
             event.error(Errors.CLIENT_DISABLED);
-            throw new ErrorPageException(session, Messages.CLIENT_DISABLED);
+            throw new ErrorPageException(session, authenticationSession, Messages.CLIENT_DISABLED);
         }
 
         if (client.isBearerOnly()) {
             event.error(Errors.NOT_ALLOWED);
-            throw new ErrorPageException(session, Messages.BEARER_ONLY);
+            throw new ErrorPageException(session, authenticationSession, Messages.BEARER_ONLY);
         }
 
         session.getContext().setClient(client);
@@ -354,7 +354,7 @@ public class AuthorizationEndpoint extends AuthorizationEndpointBase {
         redirectUri = RedirectUtils.verifyRedirectUri(uriInfo, redirectUriParam, realm, client, isOIDCRequest);
         if (redirectUri == null) {
             event.error(Errors.INVALID_REDIRECT_URI);
-            throw new ErrorPageException(session, Messages.INVALID_PARAMETER, OIDCLoginProtocol.REDIRECT_URI_PARAM);
+            throw new ErrorPageException(session, authenticationSession, Messages.INVALID_PARAMETER, OIDCLoginProtocol.REDIRECT_URI_PARAM);
         }
     }
 
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/LogoutEndpoint.java b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/LogoutEndpoint.java
index fd50025..4c58f3a 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/LogoutEndpoint.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/LogoutEndpoint.java
@@ -107,7 +107,7 @@ public class LogoutEndpoint {
                 event.event(EventType.LOGOUT);
                 event.detail(Details.REDIRECT_URI, redirect);
                 event.error(Errors.INVALID_REDIRECT_URI);
-                return ErrorPage.error(session, Messages.INVALID_REDIRECT_URI);
+                return ErrorPage.error(session, null, Messages.INVALID_REDIRECT_URI);
             }
             redirect = validatedUri;
         }
@@ -120,7 +120,7 @@ public class LogoutEndpoint {
             } catch (OAuthErrorException e) {
                 event.event(EventType.LOGOUT);
                 event.error(Errors.INVALID_TOKEN);
-                return ErrorPage.error(session, Messages.SESSION_NOT_ACTIVE);
+                return ErrorPage.error(session, null, Messages.SESSION_NOT_ACTIVE);
             }
         }
 
diff --git a/services/src/main/java/org/keycloak/protocol/RestartLoginCookie.java b/services/src/main/java/org/keycloak/protocol/RestartLoginCookie.java
index e259738..b4e037b 100644
--- a/services/src/main/java/org/keycloak/protocol/RestartLoginCookie.java
+++ b/services/src/main/java/org/keycloak/protocol/RestartLoginCookie.java
@@ -64,6 +64,10 @@ public class RestartLoginCookie {
     @JsonProperty("notes")
     protected Map<String, String> notes = new HashMap<>();
 
+    @Deprecated // Backwards compatibility
+    @JsonProperty("cs")
+    protected String cs;
+
     public Map<String, String> getNotes() {
         return notes;
     }
diff --git a/services/src/main/java/org/keycloak/protocol/saml/SamlProtocol.java b/services/src/main/java/org/keycloak/protocol/saml/SamlProtocol.java
index 66a7609..8d42b3e 100755
--- a/services/src/main/java/org/keycloak/protocol/saml/SamlProtocol.java
+++ b/services/src/main/java/org/keycloak/protocol/saml/SamlProtocol.java
@@ -173,7 +173,7 @@ public class SamlProtocol implements LoginProtocol {
                     URI redirect = builder.buildFromMap(params);
                     return Response.status(302).location(redirect).build();
                 } else {
-                    return ErrorPage.error(session, translateErrorToIdpInitiatedErrorMessage(error));
+                    return ErrorPage.error(session, authSession, translateErrorToIdpInitiatedErrorMessage(error));
                 }
             } else {
                 SAML2ErrorResponseBuilder builder = new SAML2ErrorResponseBuilder().destination(authSession.getRedirectUri()).issuer(getResponseIssuer(realm)).status(translateErrorToSAMLStatus(error).get());
@@ -196,7 +196,7 @@ public class SamlProtocol implements LoginProtocol {
                     Document document = builder.buildDocument();
                     return buildErrorResponse(authSession, binding, document);
                 } catch (Exception e) {
-                    return ErrorPage.error(session, Messages.FAILED_TO_PROCESS_RESPONSE);
+                    return ErrorPage.error(session, authSession, Messages.FAILED_TO_PROCESS_RESPONSE);
                 }
             }
         } finally {
@@ -427,7 +427,7 @@ public class SamlProtocol implements LoginProtocol {
             samlDocument = builder.buildDocument(samlModel);
         } catch (Exception e) {
             logger.error("failed", e);
-            return ErrorPage.error(session, Messages.FAILED_TO_PROCESS_RESPONSE);
+            return ErrorPage.error(session, null, Messages.FAILED_TO_PROCESS_RESPONSE);
         }
 
         JaxrsSAML2BindingBuilder bindingBuilder = new JaxrsSAML2BindingBuilder();
@@ -453,7 +453,7 @@ public class SamlProtocol implements LoginProtocol {
                 publicKey = SamlProtocolUtils.getEncryptionKey(client);
             } catch (Exception e) {
                 logger.error("failed", e);
-                return ErrorPage.error(session, Messages.FAILED_TO_PROCESS_RESPONSE);
+                return ErrorPage.error(session, null, Messages.FAILED_TO_PROCESS_RESPONSE);
             }
             bindingBuilder.encrypt(publicKey);
         }
@@ -461,7 +461,7 @@ public class SamlProtocol implements LoginProtocol {
             return buildAuthenticatedResponse(clientSession, redirectUri, samlDocument, bindingBuilder);
         } catch (Exception e) {
             logger.error("failed", e);
-            return ErrorPage.error(session, Messages.FAILED_TO_PROCESS_RESPONSE);
+            return ErrorPage.error(session, null, Messages.FAILED_TO_PROCESS_RESPONSE);
         }
     }
 
@@ -568,7 +568,7 @@ public class SamlProtocol implements LoginProtocol {
         String logoutBindingUri = userSession.getNote(SAML_LOGOUT_BINDING_URI);
         if (logoutBindingUri == null) {
             logger.error("Can't finish SAML logout as there is no logout binding set.  Please configure the logout service url in the admin console for your client applications.");
-            return ErrorPage.error(session, Messages.FAILED_LOGOUT);
+            return ErrorPage.error(session, null, Messages.FAILED_LOGOUT);
 
         }
         String logoutRelayState = userSession.getNote(SAML_LOGOUT_RELAY_STATE);
diff --git a/services/src/main/java/org/keycloak/protocol/saml/SamlService.java b/services/src/main/java/org/keycloak/protocol/saml/SamlService.java
index 8cbde8e..afea781 100755
--- a/services/src/main/java/org/keycloak/protocol/saml/SamlService.java
+++ b/services/src/main/java/org/keycloak/protocol/saml/SamlService.java
@@ -118,18 +118,18 @@ public class SamlService extends AuthorizationEndpointBase {
             if (!checkSsl()) {
                 event.event(EventType.LOGIN);
                 event.error(Errors.SSL_REQUIRED);
-                return ErrorPage.error(session, Messages.HTTPS_REQUIRED);
+                return ErrorPage.error(session, null, Messages.HTTPS_REQUIRED);
             }
             if (!realm.isEnabled()) {
                 event.event(EventType.LOGIN_ERROR);
                 event.error(Errors.REALM_DISABLED);
-                return ErrorPage.error(session, Messages.REALM_NOT_ENABLED);
+                return ErrorPage.error(session, null, Messages.REALM_NOT_ENABLED);
             }
 
             if (samlRequest == null && samlResponse == null) {
                 event.event(EventType.LOGIN);
                 event.error(Errors.INVALID_TOKEN);
-                return ErrorPage.error(session, Messages.INVALID_REQUEST);
+                return ErrorPage.error(session, null, Messages.INVALID_REQUEST);
 
             }
             return null;
@@ -142,7 +142,7 @@ public class SamlService extends AuthorizationEndpointBase {
             if (! (holder.getSamlObject() instanceof StatusResponseType)) {
                 event.detail(Details.REASON, "invalid_saml_response");
                 event.error(Errors.INVALID_SAML_RESPONSE);
-                return ErrorPage.error(session, Messages.INVALID_REQUEST);
+                return ErrorPage.error(session, null, Messages.INVALID_REQUEST);
             }
 
             StatusResponseType statusResponse = (StatusResponseType) holder.getSamlObject();
@@ -150,7 +150,7 @@ public class SamlService extends AuthorizationEndpointBase {
             if (statusResponse.getDestination() != null && !uriInfo.getAbsolutePath().toString().equals(statusResponse.getDestination())) {
                 event.detail(Details.REASON, "invalid_destination");
                 event.error(Errors.INVALID_SAML_LOGOUT_RESPONSE);
-                return ErrorPage.error(session, Messages.INVALID_REQUEST);
+                return ErrorPage.error(session, null, Messages.INVALID_REQUEST);
             }
 
             AuthenticationManager.AuthResult authResult = authManager.authenticateIdentityCookie(session, realm, false);
@@ -158,7 +158,7 @@ public class SamlService extends AuthorizationEndpointBase {
                 logger.warn("Unknown saml response.");
                 event.event(EventType.LOGOUT);
                 event.error(Errors.INVALID_TOKEN);
-                return ErrorPage.error(session, Messages.INVALID_REQUEST);
+                return ErrorPage.error(session, null, Messages.INVALID_REQUEST);
             }
             // assume this is a logout response
             UserSessionModel userSession = authResult.getSession();
@@ -167,7 +167,7 @@ public class SamlService extends AuthorizationEndpointBase {
                 logger.warn("UserSession is not tagged as logging out.");
                 event.event(EventType.LOGOUT);
                 event.error(Errors.INVALID_SAML_LOGOUT_RESPONSE);
-                return ErrorPage.error(session, Messages.INVALID_REQUEST);
+                return ErrorPage.error(session, null, Messages.INVALID_REQUEST);
             }
             logger.debug("logout response");
             Response response = authManager.browserLogout(session, realm, userSession, uriInfo, clientConnection, headers);
@@ -180,7 +180,7 @@ public class SamlService extends AuthorizationEndpointBase {
             if (documentHolder == null) {
                 event.event(EventType.LOGIN);
                 event.error(Errors.INVALID_TOKEN);
-                return ErrorPage.error(session, Messages.INVALID_REQUEST);
+                return ErrorPage.error(session, null, Messages.INVALID_REQUEST);
             }
 
             SAML2Object samlObject = documentHolder.getSamlObject();
@@ -188,7 +188,7 @@ public class SamlService extends AuthorizationEndpointBase {
             if (! (samlObject instanceof RequestAbstractType)) {
                 event.event(EventType.LOGIN);
                 event.error(Errors.INVALID_SAML_AUTHN_REQUEST);
-                return ErrorPage.error(session, Messages.INVALID_REQUEST);
+                return ErrorPage.error(session, null, Messages.INVALID_REQUEST);
             }
 
             RequestAbstractType requestAbstractType = (RequestAbstractType) samlObject;
@@ -199,23 +199,23 @@ public class SamlService extends AuthorizationEndpointBase {
                 event.event(EventType.LOGIN);
                 event.client(issuer);
                 event.error(Errors.CLIENT_NOT_FOUND);
-                return ErrorPage.error(session, Messages.UNKNOWN_LOGIN_REQUESTER);
+                return ErrorPage.error(session, null, Messages.UNKNOWN_LOGIN_REQUESTER);
             }
 
             if (!client.isEnabled()) {
                 event.event(EventType.LOGIN);
                 event.error(Errors.CLIENT_DISABLED);
-                return ErrorPage.error(session, Messages.LOGIN_REQUESTER_NOT_ENABLED);
+                return ErrorPage.error(session, null, Messages.LOGIN_REQUESTER_NOT_ENABLED);
             }
             if (client.isBearerOnly()) {
                 event.event(EventType.LOGIN);
                 event.error(Errors.NOT_ALLOWED);
-                return ErrorPage.error(session, Messages.BEARER_ONLY);
+                return ErrorPage.error(session, null, Messages.BEARER_ONLY);
             }
             if (!client.isStandardFlowEnabled()) {
                 event.event(EventType.LOGIN);
                 event.error(Errors.NOT_ALLOWED);
-                return ErrorPage.error(session, Messages.STANDARD_FLOW_DISABLED);
+                return ErrorPage.error(session, null, Messages.STANDARD_FLOW_DISABLED);
             }
 
             session.getContext().setClient(client);
@@ -226,7 +226,7 @@ public class SamlService extends AuthorizationEndpointBase {
                 SamlService.logger.error("request validation failed", e);
                 event.event(EventType.LOGIN);
                 event.error(Errors.INVALID_SIGNATURE);
-                return ErrorPage.error(session, Messages.INVALID_REQUESTER);
+                return ErrorPage.error(session, null, Messages.INVALID_REQUESTER);
             }
             logger.debug("verified request");
             if (samlObject instanceof AuthnRequestType) {
@@ -244,7 +244,7 @@ public class SamlService extends AuthorizationEndpointBase {
             } else {
                 event.event(EventType.LOGIN);
                 event.error(Errors.INVALID_TOKEN);
-                return ErrorPage.error(session, Messages.INVALID_REQUEST);
+                return ErrorPage.error(session, null, Messages.INVALID_REQUEST);
             }
         }
 
@@ -260,12 +260,12 @@ public class SamlService extends AuthorizationEndpointBase {
             if (requestAbstractType.getDestination() == null && samlClient.requiresClientSignature()) {
                 event.detail(Details.REASON, "invalid_destination");
                 event.error(Errors.INVALID_SAML_AUTHN_REQUEST);
-                return ErrorPage.error(session, Messages.INVALID_REQUEST);
+                return ErrorPage.error(session, null, Messages.INVALID_REQUEST);
             }
             if (! isValidDestination(requestAbstractType.getDestination())) {
                 event.detail(Details.REASON, "invalid_destination");
                 event.error(Errors.INVALID_SAML_AUTHN_REQUEST);
-                return ErrorPage.error(session, Messages.INVALID_REQUEST);
+                return ErrorPage.error(session, null, Messages.INVALID_REQUEST);
             }
             String bindingType = getBindingType(requestAbstractType);
             if (samlClient.forcePostBinding())
@@ -288,7 +288,7 @@ public class SamlService extends AuthorizationEndpointBase {
 
             if (redirect == null) {
                 event.error(Errors.INVALID_REDIRECT_URI);
-                return ErrorPage.error(session, Messages.INVALID_REDIRECT_URI);
+                return ErrorPage.error(session, null, Messages.INVALID_REDIRECT_URI);
             }
 
             AuthorizationEndpointChecks checks = getOrCreateAuthenticationSession(client, relayState);
@@ -316,7 +316,7 @@ public class SamlService extends AuthorizationEndpointBase {
                 } else {
                     event.detail(Details.REASON, "unsupported_nameid_format");
                     event.error(Errors.INVALID_SAML_AUTHN_REQUEST);
-                    return ErrorPage.error(session, Messages.UNSUPPORTED_NAME_ID_FORMAT);
+                    return ErrorPage.error(session, null, Messages.UNSUPPORTED_NAME_ID_FORMAT);
                 }
             }
 
@@ -367,12 +367,12 @@ public class SamlService extends AuthorizationEndpointBase {
             if (logoutRequest.getDestination() == null && samlClient.requiresClientSignature()) {
                 event.detail(Details.REASON, "invalid_destination");
                 event.error(Errors.INVALID_SAML_LOGOUT_REQUEST);
-                return ErrorPage.error(session, Messages.INVALID_REQUEST);
+                return ErrorPage.error(session, null, Messages.INVALID_REQUEST);
             }
             if (! isValidDestination(logoutRequest.getDestination())) {
                 event.detail(Details.REASON, "invalid_destination");
                 event.error(Errors.INVALID_SAML_LOGOUT_REQUEST);
-                return ErrorPage.error(session, Messages.INVALID_REQUEST);
+                return ErrorPage.error(session, null, Messages.INVALID_REQUEST);
             }
 
             // authenticate identity cookie, but ignore an access token timeout as we're logging out anyways.
@@ -620,16 +620,16 @@ public class SamlService extends AuthorizationEndpointBase {
         }
         if (client == null) {
             event.error(Errors.CLIENT_NOT_FOUND);
-            return ErrorPage.error(session, Messages.CLIENT_NOT_FOUND);
+            return ErrorPage.error(session, null, Messages.CLIENT_NOT_FOUND);
         }
         if (!client.isEnabled()) {
             event.error(Errors.CLIENT_DISABLED);
-            return ErrorPage.error(session, Messages.CLIENT_DISABLED);
+            return ErrorPage.error(session, null, Messages.CLIENT_DISABLED);
         }
         if (client.getManagementUrl() == null && client.getAttribute(SamlProtocol.SAML_ASSERTION_CONSUMER_URL_POST_ATTRIBUTE) == null && client.getAttribute(SamlProtocol.SAML_ASSERTION_CONSUMER_URL_REDIRECT_ATTRIBUTE) == null) {
             logger.error("SAML assertion consumer url not set up");
             event.error(Errors.INVALID_REDIRECT_URI);
-            return ErrorPage.error(session, Messages.INVALID_REDIRECT_URI);
+            return ErrorPage.error(session, null, Messages.INVALID_REDIRECT_URI);
         }
 
         AuthenticationSessionModel authSession = getOrCreateLoginSessionForIdpInitiatedSso(this.session, this.realm, client, relayState);
diff --git a/services/src/main/java/org/keycloak/services/ErrorPage.java b/services/src/main/java/org/keycloak/services/ErrorPage.java
index fb52ea4..291caaa 100755
--- a/services/src/main/java/org/keycloak/services/ErrorPage.java
+++ b/services/src/main/java/org/keycloak/services/ErrorPage.java
@@ -18,6 +18,7 @@ package org.keycloak.services;
 
 import org.keycloak.forms.login.LoginFormsProvider;
 import org.keycloak.models.KeycloakSession;
+import org.keycloak.sessions.AuthenticationSessionModel;
 
 import javax.ws.rs.core.Response;
 
@@ -26,8 +27,8 @@ import javax.ws.rs.core.Response;
  */
 public class ErrorPage {
 
-    public static Response error(KeycloakSession session, String message, Object... parameters) {
-        return session.getProvider(LoginFormsProvider.class).setError(message, parameters).createErrorPage();
+    public static Response error(KeycloakSession session, AuthenticationSessionModel authenticationSession, String message, Object... parameters) {
+        return session.getProvider(LoginFormsProvider.class).setAuthenticationSession(authenticationSession).setError(message, parameters).createErrorPage();
     }
 
 
diff --git a/services/src/main/java/org/keycloak/services/ErrorPageException.java b/services/src/main/java/org/keycloak/services/ErrorPageException.java
index 51ee9c8..b006718 100644
--- a/services/src/main/java/org/keycloak/services/ErrorPageException.java
+++ b/services/src/main/java/org/keycloak/services/ErrorPageException.java
@@ -18,6 +18,7 @@
 package org.keycloak.services;
 
 import org.keycloak.models.KeycloakSession;
+import org.keycloak.sessions.AuthenticationSessionModel;
 
 import javax.ws.rs.WebApplicationException;
 import javax.ws.rs.core.Response;
@@ -30,18 +31,28 @@ public class ErrorPageException extends WebApplicationException {
     private final KeycloakSession session;
     private final String errorMessage;
     private final Object[] parameters;
+    private final AuthenticationSessionModel authSession;
 
+    
     public ErrorPageException(KeycloakSession session, String errorMessage, Object... parameters) {
         this.session = session;
         this.errorMessage = errorMessage;
         this.parameters = parameters;
+        this.authSession = null;
+    }
+    
+    public ErrorPageException(KeycloakSession session, AuthenticationSessionModel authSession, String errorMessage, Object... parameters) {
+        this.session = session;
+        this.errorMessage = errorMessage;
+        this.parameters = parameters;
+        this.authSession = authSession;
     }
 
 
 
     @Override
     public Response getResponse() {
-        return ErrorPage.error(session, errorMessage, parameters);
+        return ErrorPage.error(session, authSession, errorMessage, parameters);
     }
 
 }
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 19c188a..981a47f 100755
--- a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
@@ -710,7 +710,7 @@ public class AuthenticationManager {
         }
 
         if (authSession.getAuthNote(END_AFTER_REQUIRED_ACTIONS) != null) {
-            LoginFormsProvider infoPage = session.getProvider(LoginFormsProvider.class)
+            LoginFormsProvider infoPage = session.getProvider(LoginFormsProvider.class).setAuthenticationSession(authSession)
                     .setSuccess(Messages.ACCOUNT_UPDATED);
             if (authSession.getAuthNote(SET_REDIRECT_URI_AFTER_REQUIRED_ACTIONS) != null) {
                 if (authSession.getRedirectUri() != null) {
@@ -848,6 +848,7 @@ public class AuthenticationManager {
                 authSession.setAuthNote(AuthenticationProcessor.CURRENT_AUTHENTICATION_EXECUTION, execution);
 
                 return session.getProvider(LoginFormsProvider.class)
+                        .setAuthenticationSession(authSession)
                         .setExecution(execution)
                         .setClientSessionCode(accessCode.getOrGenerateCode())
                         .setAccessRequest(realmRoles, resourceRoles, protocolMappers)
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java b/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java
index ebc89be..3d09f93 100644
--- a/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java
@@ -833,6 +833,9 @@ public class RealmAdminResource {
             if (user.getEmail() == null) {
                 return ErrorResponse.error("Logged in user does not have an e-mail.", Response.Status.INTERNAL_SERVER_ERROR);
             }
+            if (ComponentRepresentation.SECRET_VALUE.equals(settings.get("password"))) {
+                settings.put("password", realm.getSmtpConfig().get("password"));
+            }
             session.getProvider(EmailTemplateProvider.class).sendSmtpTestEmail(settings, user);
         } catch (Exception e) {
             e.printStackTrace();
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 20e23de..078cca7 100755
--- a/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java
+++ b/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java
@@ -317,12 +317,12 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
                 return response;
             }
         } catch (IdentityBrokerException e) {
-            return redirectToErrorPage(Messages.COULD_NOT_SEND_AUTHENTICATION_REQUEST, e, providerId);
+            return redirectToErrorPage(authSession, Messages.COULD_NOT_SEND_AUTHENTICATION_REQUEST, e, providerId);
         } catch (Exception e) {
-            return redirectToErrorPage(Messages.UNEXPECTED_ERROR_HANDLING_REQUEST, e, providerId);
+            return redirectToErrorPage(authSession, Messages.UNEXPECTED_ERROR_HANDLING_REQUEST, e, providerId);
         }
 
-        return redirectToErrorPage(Messages.COULD_NOT_PROCEED_WITH_AUTHENTICATION_REQUEST);
+        return redirectToErrorPage(authSession, Messages.COULD_NOT_PROCEED_WITH_AUTHENTICATION_REQUEST);
 
     }
 
@@ -545,7 +545,7 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
             return Response.status(302).location(redirect).build();
 
         } else {
-            Response response = validateUser(federatedUser, realmModel);
+            Response response = validateUser(authenticationSession, federatedUser, realmModel);
             if (response != null) {
                 return response;
             }
@@ -558,15 +558,15 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
     }
 
 
-    public Response validateUser(UserModel user, RealmModel realm) {
+    public Response validateUser(AuthenticationSessionModel authSession, UserModel user, RealmModel realm) {
         if (!user.isEnabled()) {
             event.error(Errors.USER_DISABLED);
-            return ErrorPage.error(session, Messages.ACCOUNT_DISABLED);
+            return ErrorPage.error(session, authSession, Messages.ACCOUNT_DISABLED);
         }
         if (realm.isBruteForceProtected()) {
             if (session.getProvider(BruteForceProtector.class).isTemporarilyDisabled(session, realm, user)) {
                 event.error(Errors.USER_TEMPORARILY_DISABLED);
-                return ErrorPage.error(session, Messages.ACCOUNT_DISABLED);
+                return ErrorPage.error(session, authSession, Messages.ACCOUNT_DISABLED);
             }
         }
         return null;
@@ -670,7 +670,7 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
             return finishOrRedirectToPostBrokerLogin(authSession, context, true, clientSessionCode);
 
         }  catch (Exception e) {
-            return redirectToErrorPage(Messages.IDENTITY_PROVIDER_UNEXPECTED_ERROR, e);
+            return redirectToErrorPage(authSession,Messages.IDENTITY_PROVIDER_UNEXPECTED_ERROR, e);
         }
     }
 
@@ -734,7 +734,7 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
 
             return afterPostBrokerLoginFlowSuccess(authenticationSession, context, wasFirstBrokerLogin, parsedCode.clientSessionCode);
         } catch (IdentityBrokerException e) {
-            return redirectToErrorPage(Messages.IDENTITY_PROVIDER_UNEXPECTED_ERROR, e);
+            return redirectToErrorPage(authenticationSession, Messages.IDENTITY_PROVIDER_UNEXPECTED_ERROR, e);
         }
     }
 
@@ -752,7 +752,7 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
 
                 UserModel linkingUser = AbstractIdpAuthenticator.getExistingUser(session, realmModel, authSession);
                 if (!linkingUser.getId().equals(federatedUser.getId())) {
-                    return redirectToErrorPage(Messages.IDENTITY_PROVIDER_DIFFERENT_USER_MESSAGE, federatedUser.getUsername(), linkingUser.getUsername());
+                    return redirectToErrorPage(authSession, Messages.IDENTITY_PROVIDER_DIFFERENT_USER_MESSAGE, federatedUser.getUsername(), linkingUser.getUsername());
                 }
 
                 SerializedBrokeredIdentityContext serializedCtx = SerializedBrokeredIdentityContext.readFromAuthenticationSession(authSession, AbstractIdpAuthenticator.BROKERED_CONTEXT_NOTE);
@@ -866,7 +866,7 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
         }
 
         if (!authenticatedUser.hasRole(this.realmModel.getClientByClientId(Constants.ACCOUNT_MANAGEMENT_CLIENT_ID).getRole(AccountRoles.MANAGE_ACCOUNT))) {
-            return redirectToErrorPage(Messages.INSUFFICIENT_PERMISSION);
+            return redirectToErrorPage(authSession, Messages.INSUFFICIENT_PERMISSION);
         }
 
         if (!authenticatedUser.isEnabled()) {
@@ -919,7 +919,7 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
         if (authSession.getClient() != null && authSession.getClient().getClientId().equals(Constants.ACCOUNT_MANAGEMENT_CLIENT_ID)) {
             return redirectToAccountErrorPage(authSession, message, parameters);
         } else {
-            return redirectToErrorPage(message, parameters); // Should rather redirect to app instead and display error here?
+            return redirectToErrorPage(authSession, message, parameters); // Should rather redirect to app instead and display error here?
         }
     }
 
@@ -1057,11 +1057,15 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
         return Urls.identityProviderAuthnResponse(this.uriInfo.getBaseUri(), providerId, this.realmModel.getName()).toString();
     }
 
+    private Response redirectToErrorPage(AuthenticationSessionModel authSession,String message, Object ... parameters) {
+        return redirectToErrorPage(authSession, message, null, parameters);
+    }
+    
     private Response redirectToErrorPage(String message, Object ... parameters) {
-        return redirectToErrorPage(message, null, parameters);
+        return redirectToErrorPage(null, message, null, parameters);
     }
 
-    private Response redirectToErrorPage(String message, Throwable throwable, Object ... parameters) {
+    private Response redirectToErrorPage(AuthenticationSessionModel authSession, String message, Throwable throwable, Object ... parameters) {
         if (message == null) {
             message = Messages.IDENTITY_PROVIDER_UNEXPECTED_ERROR;
         }
@@ -1073,7 +1077,7 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
             return webEx.getResponse();
         }
 
-        return ErrorPage.error(this.session, message, parameters);
+        return ErrorPage.error(this.session, authSession, message, parameters);
     }
 
     private Response redirectToAccountErrorPage(AuthenticationSessionModel authSession, String message, Object ... parameters) {
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 c4ba764..2117d28 100755
--- a/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java
+++ b/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java
@@ -340,7 +340,7 @@ public class LoginActionsService {
             if (!realm.isResetPasswordAllowed()) {
                 event.event(EventType.RESET_PASSWORD);
                 event.error(Errors.NOT_ALLOWED);
-                return ErrorPage.error(session, Messages.RESET_CREDENTIAL_NOT_ALLOWED);
+                return ErrorPage.error(session, authSession, Messages.RESET_CREDENTIAL_NOT_ALLOWED);
 
             }
             authSession = createAuthenticationSessionForClient();
@@ -384,7 +384,7 @@ public class LoginActionsService {
 
         if (!realm.isResetPasswordAllowed()) {
             event.error(Errors.NOT_ALLOWED);
-            return ErrorPage.error(session, Messages.RESET_CREDENTIAL_NOT_ALLOWED);
+            return ErrorPage.error(session, authSession, Messages.RESET_CREDENTIAL_NOT_ALLOWED);
 
         }
 
@@ -553,7 +553,7 @@ public class LoginActionsService {
         } else if (RESET_CREDENTIALS_PATH.equals(flowPath)) {
             return processResetCredentials(false, null, authSession, errorMessage);
         } else {
-            return ErrorPage.error(session, errorMessage == null ? Messages.INVALID_REQUEST : errorMessage);
+            return ErrorPage.error(session, authSession, errorMessage == null ? Messages.INVALID_REQUEST : errorMessage);
         }
     }
 
@@ -577,7 +577,7 @@ public class LoginActionsService {
         event
           .detail(Details.REASON, ex == null ? "<unknown>" : ex.getMessage())
           .error(eventError == null ? Errors.INVALID_CODE : eventError);
-        return ErrorPage.error(session, errorMessage == null ? Messages.INVALID_CODE : errorMessage);
+        return ErrorPage.error(session, null, errorMessage == null ? Messages.INVALID_CODE : errorMessage);
     }
 
     protected Response processResetCredentials(boolean actionRequest, String execution, AuthenticationSessionModel authSession, String errorMessage) {
@@ -626,7 +626,7 @@ public class LoginActionsService {
         event.event(EventType.REGISTER);
         if (!realm.isRegistrationAllowed()) {
             event.error(Errors.REGISTRATION_DISABLED);
-            return ErrorPage.error(session, Messages.REGISTRATION_NOT_ALLOWED);
+            return ErrorPage.error(session, null, Messages.REGISTRATION_NOT_ALLOWED);
         }
 
         SessionCodeChecks checks = checksForCode(code, execution, clientId, REGISTRATION_PATH);
@@ -692,7 +692,7 @@ public class LoginActionsService {
         SerializedBrokeredIdentityContext serializedCtx = SerializedBrokeredIdentityContext.readFromAuthenticationSession(authSession, noteKey);
         if (serializedCtx == null) {
             ServicesLogger.LOGGER.notFoundSerializedCtxInClientSession(noteKey);
-            throw new WebApplicationException(ErrorPage.error(session, "Not found serialized context in authenticationSession."));
+            throw new WebApplicationException(ErrorPage.error(session, authSession, "Not found serialized context in authenticationSession."));
         }
         BrokeredIdentityContext brokerContext = serializedCtx.deserialize(session, authSession);
         final String identityProviderAlias = brokerContext.getIdpConfig().getAlias();
@@ -700,12 +700,12 @@ public class LoginActionsService {
         String flowId = firstBrokerLogin ? brokerContext.getIdpConfig().getFirstBrokerLoginFlowId() : brokerContext.getIdpConfig().getPostBrokerLoginFlowId();
         if (flowId == null) {
             ServicesLogger.LOGGER.flowNotConfigForIDP(identityProviderAlias);
-            throw new WebApplicationException(ErrorPage.error(session, "Flow not configured for identity provider"));
+            throw new WebApplicationException(ErrorPage.error(session, authSession, "Flow not configured for identity provider"));
         }
         AuthenticationFlowModel brokerLoginFlow = realm.getAuthenticationFlowById(flowId);
         if (brokerLoginFlow == null) {
             ServicesLogger.LOGGER.flowNotFoundForIDP(flowId, identityProviderAlias);
-            throw new WebApplicationException(ErrorPage.error(session, "Flow not found for identity provider"));
+            throw new WebApplicationException(ErrorPage.error(session, authSession, "Flow not found for identity provider"));
         }
 
         event.detail(Details.IDENTITY_PROVIDER, identityProviderAlias)
@@ -886,7 +886,7 @@ public class LoginActionsService {
         if (factory == null) {
             ServicesLogger.LOGGER.actionProviderNull();
             event.error(Errors.INVALID_CODE);
-            throw new WebApplicationException(ErrorPage.error(session, Messages.INVALID_CODE));
+            throw new WebApplicationException(ErrorPage.error(session, authSession, Messages.INVALID_CODE));
         }
         RequiredActionProvider provider = factory.create(session);
 
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 e330d29..345f64a 100644
--- a/services/src/main/java/org/keycloak/services/resources/LoginActionsServiceChecks.java
+++ b/services/src/main/java/org/keycloak/services/resources/LoginActionsServiceChecks.java
@@ -116,7 +116,7 @@ public class LoginActionsServiceChecks {
 
         UserSessionModel userSession = context.getSession().sessions().getUserSession(context.getRealm(), authSessionId);
         if (userSession != null) {
-            LoginFormsProvider loginForm = context.getSession().getProvider(LoginFormsProvider.class)
+            LoginFormsProvider loginForm = context.getSession().getProvider(LoginFormsProvider.class).setAuthenticationSession(context.getAuthenticationSession())
               .setSuccess(Messages.ALREADY_LOGGED_IN);
 
             if (context.getSession().getContext().getClient() == null) {
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 3f68dd3..321915a 100644
--- a/services/src/main/java/org/keycloak/services/resources/SessionCodeChecks.java
+++ b/services/src/main/java/org/keycloak/services/resources/SessionCodeChecks.java
@@ -123,12 +123,12 @@ public class SessionCodeChecks {
         // Basic realm checks
         if (!checkSsl()) {
             event.error(Errors.SSL_REQUIRED);
-            response = ErrorPage.error(session, Messages.HTTPS_REQUIRED);
+            response = ErrorPage.error(session, null, Messages.HTTPS_REQUIRED);
             return null;
         }
         if (!realm.isEnabled()) {
             event.error(Errors.REALM_DISABLED);
-            response = ErrorPage.error(session, Messages.REALM_NOT_ENABLED);
+            response = ErrorPage.error(session, null, Messages.REALM_NOT_ENABLED);
             return null;
         }
 
@@ -154,7 +154,7 @@ public class SessionCodeChecks {
             UserSessionModel userSession = session.sessions().getUserSession(realm, sessionId);
             if (userSession != null) {
 
-                LoginFormsProvider loginForm = session.getProvider(LoginFormsProvider.class)
+                LoginFormsProvider loginForm = session.getProvider(LoginFormsProvider.class).setAuthenticationSession(authSession)
                         .setSuccess(Messages.ALREADY_LOGGED_IN);
 
                 if (client == null) {
@@ -190,7 +190,7 @@ public class SessionCodeChecks {
         ClientModel client = authSession.getClient();
         if (client == null) {
             event.error(Errors.CLIENT_NOT_FOUND);
-            response = ErrorPage.error(session, Messages.UNKNOWN_LOGIN_REQUESTER);
+            response = ErrorPage.error(session, authSession, Messages.UNKNOWN_LOGIN_REQUESTER);
             clientCode.removeExpiredClientSession();
             return false;
         }
@@ -200,7 +200,7 @@ public class SessionCodeChecks {
 
         if (!client.isEnabled()) {
             event.error(Errors.CLIENT_DISABLED);
-            response = ErrorPage.error(session, Messages.LOGIN_REQUESTER_NOT_ENABLED);
+            response = ErrorPage.error(session,authSession, Messages.LOGIN_REQUESTER_NOT_ENABLED);
             clientCode.removeExpiredClientSession();
             return false;
         }
@@ -285,7 +285,7 @@ public class SessionCodeChecks {
                 return false;
             } else {
                 logger.errorf("Bad action. Expected action '%s', current action '%s'", expectedAction, authSession.getAction());
-                response = ErrorPage.error(session, Messages.EXPIRED_CODE);
+                response = ErrorPage.error(session, authSession, Messages.EXPIRED_CODE);
                 return false;
             }
         }
@@ -370,7 +370,7 @@ public class SessionCodeChecks {
         } else {
             // Finally need to show error as all the fallbacks failed
             event.error(Errors.INVALID_CODE);
-            return ErrorPage.error(session, Messages.INVALID_CODE);
+            return ErrorPage.error(session, authSession, Messages.INVALID_CODE);
         }
     }
 
diff --git a/services/src/main/java/org/keycloak/services/ServicesLogger.java b/services/src/main/java/org/keycloak/services/ServicesLogger.java
index 2d6c473..98420ab 100644
--- a/services/src/main/java/org/keycloak/services/ServicesLogger.java
+++ b/services/src/main/java/org/keycloak/services/ServicesLogger.java
@@ -451,4 +451,8 @@ public interface ServicesLogger extends BasicLogger {
     @Message(id=102, value= "URL '%s' doesn't match any trustedHost or trustedDomain")
     void urlDoesntMatch(String url);
 
+    @LogMessage(level = DEBUG)
+    @Message(id=103, value="Failed to reset password. User is temporarily disabled")
+    void passwordResetFailed(@Cause Throwable t);
+
 }
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 489f73f..9a9c802 100644
--- a/services/src/main/java/org/keycloak/services/util/AuthenticationFlowURLHelper.java
+++ b/services/src/main/java/org/keycloak/services/util/AuthenticationFlowURLHelper.java
@@ -56,7 +56,7 @@ public class AuthenticationFlowURLHelper {
 
         logger.debugf("Redirecting to 'page expired' now. Will use last step URL: %s", lastStepUrl);
 
-        return session.getProvider(LoginFormsProvider.class)
+        return session.getProvider(LoginFormsProvider.class).setAuthenticationSession(authSession)
                 .setActionUri(lastStepUrl)
                 .setExecution(getExecutionId(authSession))
                 .createLoginExpiredPage();
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 1f456cf..afcbf4e 100755
--- a/services/src/main/java/org/keycloak/social/twitter/TwitterIdentityProvider.java
+++ b/services/src/main/java/org/keycloak/social/twitter/TwitterIdentityProvider.java
@@ -191,14 +191,13 @@ public class TwitterIdentityProvider extends AbstractIdentityProvider<OAuth2Iden
                 return callback.cancelled(state);
             }
 
-            Response errorResponse = null;
-
+            AuthenticationSessionModel authSession = null;
             try {
                 Twitter twitter = new TwitterFactory().getInstance();
 
                 twitter.setOAuthConsumer(getConfig().getClientId(), getConfig().getClientSecret());
 
-                AuthenticationSessionModel authSession = ClientSessionCode.getClientSession(state, session, realm, event, AuthenticationSessionModel.class);
+                authSession = ClientSessionCode.getClientSession(state, session, realm, event, AuthenticationSessionModel.class);
 
                 String twitterToken = authSession.getAuthNote(TWITTER_TOKEN);
                 String twitterSecret = authSession.getAuthNote(TWITTER_TOKENSECRET);
@@ -239,7 +238,7 @@ public class TwitterIdentityProvider extends AbstractIdentityProvider<OAuth2Iden
             } catch (Exception e) {
                 logger.error("Could get user profile from twitter.", e);
                 sendErrorEvent();
-                return ErrorPage.error(session, Messages.UNEXPECTED_ERROR_HANDLING_RESPONSE);
+                return ErrorPage.error(session, authSession, Messages.UNEXPECTED_ERROR_HANDLING_RESPONSE);
             }
         }
 
diff --git a/testsuite/integration-arquillian/HOW-TO-RUN.md b/testsuite/integration-arquillian/HOW-TO-RUN.md
index 4033559..8181561 100644
--- a/testsuite/integration-arquillian/HOW-TO-RUN.md
+++ b/testsuite/integration-arquillian/HOW-TO-RUN.md
@@ -446,39 +446,64 @@ and argument: `-p 8181`
 
 ## Cross-DC tests
 
-Cross-DC tests use 2 data centers, each with one automatically started and one manually controlled backend servers
-(currently only Keycloak on Undertow), and 1 frontend loadbalancer server node that sits in front of all servers.
+Cross-DC tests use 2 data centers, each with one automatically started and one manually controlled backend servers, 
+and 1 frontend loadbalancer server node that sits in front of all servers.
 The browser usually communicates directly with the frontent node and the test controls where the HTTP requests
 land by adjusting load balancer configuration (e.g. to direct the traffic to only a single DC).
 
 For an example of a test, see [org.keycloak.testsuite.crossdc.ActionTokenCrossDCTest](tests/base/src/test/java/org/keycloak/testsuite/crossdc/ActionTokenCrossDCTest.java).
 
-The cross DC requires setting a profile specifying used cache server (currently only Infinispan) by specifying
-`cache-server-infinispan` profile in maven.
+The cross DC requires setting a profile specifying used cache server by specifying
+`cache-server-infinispan` or `cache-server-jdg` profile in maven.
 
 #### Run Cross-DC Tests from Maven
 
-First compile the Infinispan/JDG test server via the following command:
+a) First compile the Infinispan/JDG test server via the following command:
 
-  `mvn -Pcache-server-infinispan -f testsuite/integration-arquillian -DskipTests clean install`
+  `mvn -Pcache-server-infinispan,auth-servers-crossdc-undertow -f testsuite/integration-arquillian -DskipTests clean install`
 
 or
 
-  `mvn -Pcache-server-jdg -f testsuite/integration-arquillian -DskipTests clean install`
+  `mvn -Pcache-server-jdg,auth-servers-crossdc-undertow -f testsuite/integration-arquillian -DskipTests clean install`
 
-Then you can run the tests using the following command (adjust the test specification according to your needs):
+b) Then in case you want to use **JBoss-based** Keycloak backend containers instead of containers on Embedded Undertow run following command:
 
-  `mvn -Pcache-server-infinispan -Dtest=*.crossdc.* -pl testsuite/integration-arquillian/tests/base test`
+    `mvn -Pauth-servers-crossdc-jboss,auth-server-wildfly -f testsuite/integration-arquillian -DskipTests clean install`
+
+*note: 'auth-server-wildfly' can be replaced by 'auth-server-eap'*
+
+By default JBoss-based containers use in-memory h2 database. It can be configured to use real DB, e.g. with following command:
+
+  `mvn -Pauth-servers-crossdc-jboss,auth-server-wildfly,jpa -f testsuite/integration-arquillian -DskipTests clean install -Djdbc.mvn.groupId=org.mariadb.jdbc -Djdbc.mvn.artifactId=mariadb-java-client -Djdbc.mvn.version=2.0.3 -Dkeycloak.connectionsJpa.url=jdbc:mariadb://localhost:3306/keycloak -Dkeycloak.connectionsJpa.password=keycloak -Dkeycloak.connectionsJpa.user=keycloak`
+
+c1) Then you can run the tests using the following command (adjust the test specification according to your needs) for Keycloak backend containers on **Undertow**:
+
+  `mvn -Pcache-server-infinispan,auth-servers-crossdc-undertow -Dtest=*.crossdc.* -pl testsuite/integration-arquillian/tests/base clean install`
 
 or
 
-  `mvn -Pcache-server-jdg -Dtest=*.crossdc.* -pl testsuite/integration-arquillian/tests/base test`
-  
+  `mvn -Pcache-server-jdg,auth-servers-crossdc-undertow -Dtest=*.crossdc.* -pl testsuite/integration-arquillian/tests/base clean install`
+
+c2) For **JBoss-based** Keycloak backend containers:
+
+  `mvn -Pcache-server-infinispan,auth-servers-crossdc-jboss,auth-server-wildfly -Dtest=*.crossdc.* -pl testsuite/integration-arquillian/tests/base clean install`
+
+or
+
+  `mvn -Pcache-server-jdg,auth-servers-crossdc-jboss,auth-server-wildfly -Dtest=*.crossdc.* -pl testsuite/integration-arquillian/tests/base clean install`
+
+*note: 'auth-server-wildfly can be replaced by auth-server-eap'*
+
+**note**
+Previous commands can be "squashed" into one. E.g.:
+
+  `mvn -f testsuite/integration-arquillian clean install -Dtest=*.crossdc.* -Djdbc.mvn.groupId=org.mariadb.jdbc -Djdbc.mvn.artifactId=mariadb-java-client -Djdbc.mvn.version=2.0.3 -Dkeycloak.connectionsJpa.url=jdbc:mariadb://localhost:3306/keycloak -Dkeycloak.connectionsJpa.password=keycloak -Dkeycloak.connectionsJpa.user=keycloak -Pcache-server-infinispan,auth-servers-crossdc-jboss,auth-server-wildfly,jpa clean install`
+
 It can be useful to add additional system property to enable logging:
   
     -Dkeycloak.infinispan.logging.level=debug
     
-Tests from package "manual" uses manual lifecycle for all servers, so needs to be executed manually. Also needs to be executed with real DB like MySQL. You can run them with:
+**Tests from package "manual"** uses manual lifecycle for all servers, so needs to be executed manually. Also needs to be executed with real DB like MySQL. You can run them with:
 
     mvn -Pcache-server-infinispan -Dtest=*.crossdc.manual.* -Dmanual.mode=true \
     -Dkeycloak.connectionsJpa.url.crossdc=jdbc:mysql://localhost/keycloak -Dkeycloak.connectionsJpa.driver.crossdc=com.mysql.jdbc.Driver \
diff --git a/testsuite/integration-arquillian/pom.xml b/testsuite/integration-arquillian/pom.xml
index c5674ff..c21ea8f 100644
--- a/testsuite/integration-arquillian/pom.xml
+++ b/testsuite/integration-arquillian/pom.xml
@@ -45,7 +45,7 @@
         <selenium.version>3.5.3</selenium.version>
         <arquillian-drone.version>2.4.2</arquillian-drone.version>
         <arquillian-graphene.version>2.3.1</arquillian-graphene.version>
-        <arquillian-wildfly-container.version>2.1.0.Beta1</arquillian-wildfly-container.version>
+        <arquillian-wildfly-container.version>2.1.0.Final</arquillian-wildfly-container.version>
         <arquillian-wls-container.version>1.0.1.Final</arquillian-wls-container.version>
         <arquillian-infinispan-container.version>1.2.0.Beta2</arquillian-infinispan-container.version>
         <version.shrinkwrap.resolvers>2.2.6</version.shrinkwrap.resolvers>
@@ -56,7 +56,7 @@
         <migration.70.version>1.9.8.Final</migration.70.version>
         <migration.70.authz.version>2.2.1.Final</migration.70.authz.version>
         <migration.71.version>2.5.5.Final</migration.71.version>
-        
+
         <maven.compiler.target>1.8</maven.compiler.target>
         <maven.compiler.source>1.8</maven.compiler.source>
     </properties>
diff --git a/testsuite/integration-arquillian/servers/app-server/jboss/common/install-adapters.sh b/testsuite/integration-arquillian/servers/app-server/jboss/common/install-adapters.sh
index bd9e65b..d170a1e 100755
--- a/testsuite/integration-arquillian/servers/app-server/jboss/common/install-adapters.sh
+++ b/testsuite/integration-arquillian/servers/app-server/jboss/common/install-adapters.sh
@@ -26,6 +26,8 @@ do
 
         if [ "$ELYTRON_SUPPORTED" = true ]; then
             ./jboss-cli.sh -c --file="adapter-elytron-install.cli"
+        else
+            ./jboss-cli.sh -c --command="/subsystem=elytron:remove"
         fi
 
         if [ $? -ne 0 ]; then RESULT=1; fi
diff --git a/testsuite/integration-arquillian/servers/auth-server/jboss/common/crossdc/cross-dc-setup.cli b/testsuite/integration-arquillian/servers/auth-server/jboss/common/crossdc/cross-dc-setup.cli
new file mode 100644
index 0000000..836cde1
--- /dev/null
+++ b/testsuite/integration-arquillian/servers/auth-server/jboss/common/crossdc/cross-dc-setup.cli
@@ -0,0 +1,129 @@
+embed-server --server-config=standalone-ha.xml
+
+echo **** Begin ****
+
+echo *** Update jgoups subsystem ***
+/subsystem=jgroups/stack=udp/transport=UDP:write-attribute(name=site, value=${jboss.site.name})
+
+echo *** Update infinispan subsystem ***
+/subsystem=infinispan/cache-container=keycloak:write-attribute(name=module, value=org.keycloak.keycloak-model-infinispan)
+
+echo ** Update replicated-cache work element **
+/subsystem=infinispan/cache-container=keycloak/replicated-cache=work/store=custom:add( \
+    class=org.keycloak.models.sessions.infinispan.remotestore.KeycloakRemoteStoreConfigurationBuilder, \
+    passivation=false, \
+    fetch-state=false, \
+    purge=false, \
+    preload=false, \
+    shared=true \
+)
+
+/subsystem=infinispan/cache-container=keycloak/replicated-cache=work/store=custom:write-attribute( \
+    name=properties, value={ \
+        rawValues=true, \
+        marshaller=org.keycloak.cluster.infinispan.KeycloakHotRodMarshallerFactory, \
+        transportFactory=org.keycloak.models.sessions.infinispan.remotestore.KeycloakTcpTransportFactory, \
+        remoteServers=localhost:${remote.cache.port}, \
+        remoteCacheName=work, \
+        sessionCache=false \
+    } \
+)
+
+/subsystem=infinispan/cache-container=keycloak/replicated-cache=work:write-attribute(name=statistics-enabled,value=true)
+
+echo ** Update distributed-cache sessions element **
+/subsystem=infinispan/cache-container=keycloak/distributed-cache=sessions/store=custom:add( \
+    class=org.keycloak.models.sessions.infinispan.remotestore.KeycloakRemoteStoreConfigurationBuilder, \
+    passivation=false, \
+    fetch-state=false, \
+    purge=false, \
+    preload=false, \
+    shared=true \
+)
+
+/subsystem=infinispan/cache-container=keycloak/distributed-cache=sessions/store=custom:write-attribute( \
+    name=properties, value={ \
+        remoteCacheName=sessions, \
+        useConfigTemplateFromCache=work, \
+        sessionCache=true \
+    } \
+)
+
+/subsystem=infinispan/cache-container=keycloak/distributed-cache=sessions:write-attribute(name=statistics-enabled,value=true)
+
+echo ** Update distributed-cache offlineSessions element **
+/subsystem=infinispan/cache-container=keycloak/distributed-cache=offlineSessions/store=custom:add( \
+    class=org.keycloak.models.sessions.infinispan.remotestore.KeycloakRemoteStoreConfigurationBuilder, \
+    passivation=false, \
+    fetch-state=false, \
+    purge=false, \
+    preload=false, \
+    shared=true \
+)
+
+/subsystem=infinispan/cache-container=keycloak/distributed-cache=offlineSessions/store=custom:write-attribute( \
+    name=properties, value={ \
+        remoteCacheName=offlineSessions, \
+        useConfigTemplateFromCache=work, \
+        sessionCache=true \
+    } \
+)
+
+/subsystem=infinispan/cache-container=keycloak/distributed-cache=offlineSessions:write-attribute(name=statistics-enabled,value=true)
+
+echo ** Update distributed-cache loginFailures element **
+/subsystem=infinispan/cache-container=keycloak/distributed-cache=loginFailures/store=custom:add( \
+    class=org.keycloak.models.sessions.infinispan.remotestore.KeycloakRemoteStoreConfigurationBuilder, \
+    passivation=false, \
+    fetch-state=false, \
+    purge=false, \
+    preload=false, \
+    shared=true \
+)
+
+/subsystem=infinispan/cache-container=keycloak/distributed-cache=loginFailures/store=custom:write-attribute( \
+    name=properties, value={ \
+        remoteCacheName=loginFailures, \
+        useConfigTemplateFromCache=work, \
+        sessionCache=true \
+    } \
+)
+
+/subsystem=infinispan/cache-container=keycloak/distributed-cache=loginFailures:write-attribute(name=statistics-enabled,value=true)
+
+echo ** Update distributed-cache actionTokens element **
+/subsystem=infinispan/cache-container=keycloak/distributed-cache=actionTokens/store=custom:add( \
+    class=org.keycloak.models.sessions.infinispan.remotestore.KeycloakRemoteStoreConfigurationBuilder, \
+    passivation=false, \
+    fetch-state=false, \
+    purge=false, \
+    preload=true, \
+    shared=true \
+)
+
+/subsystem=infinispan/cache-container=keycloak/distributed-cache=actionTokens/store=custom:write-attribute( \
+    name=properties, value={ \
+        remoteCacheName=actionTokens, \
+        useConfigTemplateFromCache=work, \
+        sessionCache=false \
+    } \
+)
+
+/subsystem=infinispan/cache-container=keycloak/distributed-cache=actionTokens:write-attribute(name=statistics-enabled,value=true)
+
+echo ** Update distributed-cache authenticationSessions element **
+/subsystem=infinispan/cache-container=keycloak/distributed-cache=authenticationSessions:write-attribute(name=statistics-enabled,value=true)
+
+echo *** Enable debug logging ***
+/subsystem=logging/logger=org.keycloak.cluster.infinispan:add(level=DEBUG)
+
+/subsystem=logging/logger=org.keycloak.connections.infinispan:add(level=DEBUG)
+
+/subsystem=logging/logger=org.keycloak.models.cache.infinispan:add(level=DEBUG)
+
+/subsystem=logging/logger=org.keycloak.models.sessions.infinispan:add(level=DEBUG)
+
+echo *** Update undertow subsystem ***
+/subsystem=undertow/server=default-server/http-listener=default:write-attribute(name=proxy-address-forwarding,value=true)
+
+echo **** End ****
diff --git a/testsuite/integration-arquillian/servers/auth-server/jboss/eap/pom.xml b/testsuite/integration-arquillian/servers/auth-server/jboss/eap/pom.xml
index 47e6a7f..95b33ba 100644
--- a/testsuite/integration-arquillian/servers/auth-server/jboss/eap/pom.xml
+++ b/testsuite/integration-arquillian/servers/auth-server/jboss/eap/pom.xml
@@ -26,11 +26,11 @@
     <modelVersion>4.0.0</modelVersion>
 
     <packaging>pom</packaging>
-    
+
     <artifactId>integration-arquillian-servers-auth-server-eap</artifactId>
-    
+
     <name>Auth Server - JBoss - EAP</name>
-    
+
     <properties>
         <auth.server.jboss>eap</auth.server.jboss>
         <auth.server.home>${project.build.directory}/unpacked/${product.unpacked.folder.name}</auth.server.home>
@@ -57,6 +57,7 @@
                             <goal>enforce</goal>
                         </goals>
                         <configuration>
+                            <skip>false</skip>
                             <rules>
                                 <requireProperty>
                                     <property>product.version</property>
@@ -71,5 +72,5 @@
             </plugin>
         </plugins>
     </build>
-            
+
 </project>
diff --git a/testsuite/integration-arquillian/servers/auth-server/jboss/pom.xml b/testsuite/integration-arquillian/servers/auth-server/jboss/pom.xml
index ae7d87a..26a3323 100644
--- a/testsuite/integration-arquillian/servers/auth-server/jboss/pom.xml
+++ b/testsuite/integration-arquillian/servers/auth-server/jboss/pom.xml
@@ -191,6 +191,7 @@
                                             <dir>${auth.server.home}/standalone/configuration</dir>
                                             <includes>
                                                 <include>standalone.xml</include>
+                                                <include>standalone-ha.xml</include>
                                             </includes>
                                             <stylesheet>${common.resources}/keycloak-server-subsystem.xsl</stylesheet>
                                             <outputDir>${auth.server.home}/standalone/configuration</outputDir>
@@ -575,6 +576,120 @@
                 </pluginManagement>
             </build>
         </profile>
+        
+        <profile>
+            <id>auth-servers-crossdc-jboss</id>
+            <properties>
+                <crossdc.jboss.jdbc.url>jdbc:h2:tcp://localhost:9092/mem:keycloak-dc-shared;DB_CLOSE_DELAY=-1</crossdc.jboss.jdbc.url>
+            </properties>
+            <build>
+                <plugins>
+                    <plugin>
+                        <artifactId>maven-enforcer-plugin</artifactId>
+                        <configuration>
+                            <skip>true</skip>
+                        </configuration>
+                    </plugin>
+                </plugins>
+                <pluginManagement>
+                    <plugins>
+                        <plugin>
+                            <artifactId>maven-enforcer-plugin</artifactId>
+                            <executions>
+                                <execution>
+                                    <id>enforce-profile-activation</id>
+                                    <goals>
+                                        <goal>enforce</goal>
+                                    </goals>
+                                    <configuration>
+                                        <rules>
+                                            <requireProperty>
+                                                <property>auth.server.jboss</property>
+                                                <message>Profile "auth-servers-crossdc-jboss" requires activation of another profile: either "auth-server-wildfly" or "auth-server-eap".</message>
+                                                <regex>(wildfly|eap)</regex>
+                                            </requireProperty>
+                                        </rules>
+                                    </configuration>
+                                </execution>
+                            </executions>
+                        </plugin>
+                        <plugin>
+                            <groupId>org.codehaus.mojo</groupId>
+                            <artifactId>xml-maven-plugin</artifactId>
+                            <executions>
+                                <execution>
+                                    <id>jpa-h2-tcp</id>
+                                    <phase>process-resources</phase>
+                                    <goals>
+                                        <goal>transform</goal>
+                                    </goals>
+                                    <configuration>
+                                        <skip>${skip.h2.tcp}</skip>
+                                        <transformationSets>
+                                            <transformationSet>
+                                                <dir>${auth.server.home}/standalone/configuration</dir>
+                                                <includes>
+                                                    <include>standalone-ha.xml</include>
+                                                </includes>
+                                                <stylesheet>${common.resources}/datasource-jdbc-url.xsl</stylesheet>
+                                                <outputDir>${auth.server.home}/standalone/configuration</outputDir>
+                                                <parameters>
+                                                    <parameter>
+                                                        <name>pool.name</name>
+                                                        <value>KeycloakDS</value>
+                                                    </parameter>
+                                                    <parameter>
+                                                        <name>jdbc.url</name>
+                                                        <value>${crossdc.jboss.jdbc.url}</value>
+                                                    </parameter>
+                                                </parameters>
+                                            </transformationSet>
+                                        </transformationSets>
+                                    </configuration>
+                                </execution>
+                            </executions>
+                        </plugin>
+                        <plugin>
+                            <groupId>org.codehaus.mojo</groupId>
+                            <artifactId>exec-maven-plugin</artifactId>
+                            <executions>
+                                <execution>
+                                    <id>crossdc-setup</id>
+                                    <phase>process-resources</phase>
+                                    <goals>
+                                        <goal>exec</goal>
+                                    </goals>
+                                    <configuration>
+                                        <executable>${auth.server.home}/bin/jboss-cli.sh</executable>
+                                        <arguments>
+                                            <argument>--file=${common.resources}/crossdc/cross-dc-setup.cli</argument>
+                                        </arguments>
+                                    </configuration>
+                                </execution>
+                                <execution>
+                                    <id>remove-temp-data-crossdc-setup</id>
+                                    <phase>process-resources</phase>
+                                    <goals>
+                                        <goal>exec</goal>
+                                    </goals>
+                                    <configuration>
+                                        <workingDirectory>${auth.server.home}/standalone/</workingDirectory>
+                                        <executable>rm</executable>
+                                        <arguments>
+                                            <argument>-rf</argument>
+                                            <argument>data</argument>
+                                            <argument>log</argument>
+                                            <argument>tmp</argument>
+                                        </arguments>
+                                    </configuration>
+                                </execution>
+                            </executions>
+                        </plugin>
+                    </plugins>
+                </pluginManagement>
+            </build>
+        </profile>
+        
         <profile>
             <id>auth-server-cluster</id>
             <properties>
diff --git a/testsuite/integration-arquillian/servers/auth-server/jboss/wildfly/pom.xml b/testsuite/integration-arquillian/servers/auth-server/jboss/wildfly/pom.xml
index c4f8f52..ab6f60f 100644
--- a/testsuite/integration-arquillian/servers/auth-server/jboss/wildfly/pom.xml
+++ b/testsuite/integration-arquillian/servers/auth-server/jboss/wildfly/pom.xml
@@ -26,9 +26,9 @@
     <modelVersion>4.0.0</modelVersion>
 
     <packaging>pom</packaging>
-    
+
     <artifactId>integration-arquillian-servers-auth-server-wildfly</artifactId>
-    
+
     <name>Auth Server - JBoss - Wildfly</name>
 
     <dependencies>
@@ -38,9 +38,20 @@
             <type>zip</type>
         </dependency>
     </dependencies>
-    
+
     <properties>
         <auth.server.jboss>wildfly</auth.server.jboss>
     </properties>
-    
+
+    <build>
+        <plugins>
+            <plugin>
+                <artifactId>maven-enforcer-plugin</artifactId>
+                <configuration>
+                    <skip>false</skip>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
 </project>
diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/org/keycloak/testsuite/integration-arquillian-testsuite-providers/main/module.xml b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/org/keycloak/testsuite/integration-arquillian-testsuite-providers/main/module.xml
index 1e4b3f7..467601a 100644
--- a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/org/keycloak/testsuite/integration-arquillian-testsuite-providers/main/module.xml
+++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/org/keycloak/testsuite/integration-arquillian-testsuite-providers/main/module.xml
@@ -33,6 +33,7 @@
         <module name="org.keycloak.keycloak-model-infinispan"/>
         <module name="org.keycloak.keycloak-model-jpa"/>
         <module name="org.infinispan"/>
+        <module name="org.infinispan.client.hotrod"/>
         <module name="org.jboss.logging"/>
         <module name="org.jboss.resteasy.resteasy-jaxrs"/>
         <module name="javax.persistence.api"/>
diff --git a/testsuite/integration-arquillian/servers/auth-server/undertow/src/main/java/org/keycloak/testsuite/arquillian/undertow/lb/SimpleUndertowLoadBalancer.java b/testsuite/integration-arquillian/servers/auth-server/undertow/src/main/java/org/keycloak/testsuite/arquillian/undertow/lb/SimpleUndertowLoadBalancer.java
index 3da888a..26be8f7 100644
--- a/testsuite/integration-arquillian/servers/auth-server/undertow/src/main/java/org/keycloak/testsuite/arquillian/undertow/lb/SimpleUndertowLoadBalancer.java
+++ b/testsuite/integration-arquillian/servers/auth-server/undertow/src/main/java/org/keycloak/testsuite/arquillian/undertow/lb/SimpleUndertowLoadBalancer.java
@@ -17,7 +17,12 @@
 
 package org.keycloak.testsuite.arquillian.undertow.lb;
 
+import java.lang.reflect.Field;
 import java.net.URI;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
 import java.util.Map;
 import java.util.concurrent.TimeUnit;
 
@@ -34,6 +39,7 @@ import io.undertow.server.handlers.proxy.ProxyHandler;
 import io.undertow.util.AttachmentKey;
 import io.undertow.util.Headers;
 import org.jboss.logging.Logger;
+import org.keycloak.common.util.reflections.Reflections;
 import org.keycloak.services.managers.AuthenticationSessionManager;
 import java.util.LinkedHashMap;
 import java.util.StringTokenizer;
@@ -94,7 +100,7 @@ public class SimpleUndertowLoadBalancer {
                     .build();
             undertow.start();
 
-            log.infof("Loadbalancer started and ready to serve requests on http://%s:%d", host, port);
+            log.infof("#### Loadbalancer started and ready to serve requests on http://%s:%d ####", host, port);
         } catch (Exception e) {
             throw new RuntimeException(e);
         }
@@ -110,11 +116,12 @@ public class SimpleUndertowLoadBalancer {
             lb.removeHost(uri);
             lb.addHost(uri, route);
         });
+        log.infof("Load balancer: enable all nodes. All enabled nodes: %s", lb.toString());
     }
 
     public void disableAllBackendNodes() {
-        log.debugf("Load balancer: disabling all nodes");
         backendNodes.values().forEach(lb::removeHost);
+        log.infof("Load balancer: disabling all nodes");
     }
 
     public void enableBackendNodeByName(String nodeName) {
@@ -122,8 +129,8 @@ public class SimpleUndertowLoadBalancer {
         if (uri == null) {
             throw new IllegalArgumentException("Invalid node: " + nodeName);
         }
-        log.debugf("Load balancer: enabling node %s", nodeName);
         lb.addHost(uri, nodeName);
+        log.infof("Load balancer: enabled node '%s', All enabled nodes: %s", nodeName, lb.toString());
     }
 
     public void disableBackendNodeByName(String nodeName) {
@@ -131,8 +138,8 @@ public class SimpleUndertowLoadBalancer {
         if (uri == null) {
             throw new IllegalArgumentException("Invalid node: " + nodeName);
         }
-        log.debugf("Load balancer: disabling node %s", nodeName);
         lb.removeHost(uri);
+        log.infof("Load balancer: disabled node '%s', All enabled nodes: %s", nodeName, lb.toString());
     }
 
     static Map<String, URI> parseNodes(String nodes) {
@@ -225,6 +232,19 @@ public class SimpleUndertowLoadBalancer {
         }
 
 
+        // For now, overriden just this "addHost" method to avoid adding duplicates
+        @Override
+        public synchronized LoadBalancingProxyClient addHost(URI host, String jvmRoute) {
+            List<String> current = getCurrentHostRoutes();
+            if (current.contains(jvmRoute)) {
+                log.infof("Route '%s' already present. Skip adding", jvmRoute);
+                return this;
+            } else {
+                return super.addHost(host, jvmRoute);
+            }
+        }
+
+
         @Override
         public void getConnection(ProxyTarget target, HttpServerExchange exchange, ProxyCallback<ProxyConnection> callback, long timeout, TimeUnit timeUnit) {
             long timeoutMs = timeUnit.toMillis(timeout);
@@ -233,6 +253,31 @@ public class SimpleUndertowLoadBalancer {
             super.getConnection(target, exchange, callbackDelegate, timeout, timeUnit);
         }
 
+
+        @Override
+        public String toString() {
+            return getCurrentHostRoutes().toString();
+        }
+
+
+        private List<String> getCurrentHostRoutes() {
+            Field hostsField = Reflections.findDeclaredField(LoadBalancingProxyClient.class, "hosts");
+            hostsField.setAccessible(true);
+            Host[] hosts = (Host[]) Reflections.getFieldValue(hostsField, this);
+
+            if (hosts == null) {
+                return Collections.emptyList();
+            }
+
+            List<String> hostRoutes = new LinkedList<>();
+            for (Host host : hosts) {
+                Field hostField = Reflections.findDeclaredField(Host.class, "jvmRoute");
+                hostField.setAccessible(true);
+                String route = Reflections.getFieldValue(hostField, host).toString();
+                hostRoutes.add(route);
+            }
+            return hostRoutes;
+        }
     }
 
 
diff --git a/testsuite/integration-arquillian/tests/base/pom.xml b/testsuite/integration-arquillian/tests/base/pom.xml
index bd7b3d7..9b26254 100644
--- a/testsuite/integration-arquillian/tests/base/pom.xml
+++ b/testsuite/integration-arquillian/tests/base/pom.xml
@@ -228,6 +228,13 @@
                     </execution>
                 </executions>
             </plugin>
+
+            <plugin>
+                <artifactId>maven-antrun-plugin</artifactId>
+                <configuration>
+                    <skip>false</skip>
+                </configuration>
+            </plugin>
         </plugins>
 
     </build>
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/AuthServerTestEnricher.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/AuthServerTestEnricher.java
index 5b0f6d8..d2092ff 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/AuthServerTestEnricher.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/AuthServerTestEnricher.java
@@ -135,6 +135,11 @@ public class AuthServerTestEnricher {
 
         return managementClient;
     }
+    
+    public void distinguishContainersInConsoleOutput(@Observes(precedence = 5) StartContainer event) {
+        log.info("*****************************************************************"
+                + "*****************************************************************************");
+    }
 
     public void initializeSuiteContext(@Observes(precedence = 2) BeforeSuite event) {
         Set<ContainerInfo> containers = containerRegistry.get().getContainers().stream()
@@ -165,15 +170,16 @@ public class AuthServerTestEnricher {
             }
 
             containers.stream()
-              .filter(c -> c.getQualifier().startsWith(AUTH_SERVER_CONTAINER + "-cross-dc-"))
-              .sorted((a, b) -> a.getQualifier().compareTo(b.getQualifier()))
-              .forEach(c -> {
-                String portOffsetString = c.getArquillianContainer().getContainerConfiguration().getContainerProperties().getOrDefault("bindHttpPortOffset", "0");
-                String dcString = c.getArquillianContainer().getContainerConfiguration().getContainerProperties().getOrDefault("dataCenter", "0");
-                updateWithAuthServerInfo(c, Integer.valueOf(portOffsetString));
-                suiteContext.addAuthServerBackendsInfo(Integer.valueOf(dcString), c);
-              });
+                    .filter(c -> c.getQualifier().startsWith("auth-server-" + System.getProperty("node.name") + "-"))
+                    .sorted((a, b) -> a.getQualifier().compareTo(b.getQualifier()))
+                    .forEach(c -> {
+                        String portOffsetString = c.getArquillianContainer().getContainerConfiguration().getContainerProperties().getOrDefault("bindHttpPortOffset", "0");
+                        updateWithAuthServerInfo(c, Integer.valueOf(portOffsetString));
 
+                        String dcString = c.getArquillianContainer().getContainerConfiguration().getContainerProperties().getOrDefault("dataCenter", "0");
+                        suiteContext.addAuthServerBackendsInfo(Integer.valueOf(dcString), c);
+                    });
+            
             containers.stream()
                     .filter(c -> c.getQualifier().startsWith("cache-server-cross-dc-"))
                     .sorted((a, b) -> a.getQualifier().compareTo(b.getQualifier()))
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/CacheStatisticsControllerEnricher.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/CacheStatisticsControllerEnricher.java
index 33af2f2..04c6110 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/CacheStatisticsControllerEnricher.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/CacheStatisticsControllerEnricher.java
@@ -1,23 +1,18 @@
 package org.keycloak.testsuite.arquillian;
 
-import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
-import org.keycloak.testsuite.Retry;
-import java.util.Map;
-import org.jboss.arquillian.core.api.Instance;
-import org.jboss.arquillian.core.api.annotation.Inject;
+import java.io.IOException;
+import java.io.NotSerializableException;
+import java.lang.management.ManagementFactory;
 import java.lang.reflect.Field;
 import java.lang.reflect.Method;
-import java.net.MalformedURLException;
-import javax.management.MBeanServerConnection;
-import javax.management.ObjectName;
-import javax.management.remote.JMXConnector;
-import javax.management.remote.JMXServiceURL;
-import org.jboss.arquillian.container.spi.Container;
-import org.jboss.arquillian.container.spi.ContainerRegistry;
-import org.jboss.arquillian.test.spi.TestEnricher;
-import java.io.IOException;
 import java.lang.reflect.Parameter;
+import java.net.MalformedURLException;
 import java.util.Arrays;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Supplier;
 import java.util.stream.Collectors;
 import javax.management.Attribute;
 import javax.management.AttributeNotFoundException;
@@ -26,21 +21,26 @@ import javax.management.IntrospectionException;
 import javax.management.MBeanAttributeInfo;
 import javax.management.MBeanException;
 import javax.management.MBeanInfo;
+import javax.management.MBeanServerConnection;
 import javax.management.MalformedObjectNameException;
+import javax.management.ObjectName;
 import javax.management.ReflectionException;
+import javax.management.remote.JMXServiceURL;
+import org.apache.commons.lang3.reflect.FieldUtils;
+import org.jboss.arquillian.container.spi.Container;
+import org.jboss.arquillian.container.spi.ContainerRegistry;
+import org.jboss.arquillian.core.api.Instance;
+import org.jboss.arquillian.core.api.annotation.Inject;
+import org.jboss.arquillian.core.spi.Validate;
+import org.jboss.arquillian.test.spi.TestEnricher;
+import org.jboss.logging.Logger;
+import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
+import org.keycloak.testsuite.Retry;
 import org.keycloak.testsuite.arquillian.annotation.JmxInfinispanCacheStatistics;
-import java.util.Set;
 import org.keycloak.testsuite.arquillian.annotation.JmxInfinispanChannelStatistics;
 import org.keycloak.testsuite.arquillian.jmx.JmxConnectorRegistry;
 import org.keycloak.testsuite.arquillian.undertow.KeycloakOnUndertow;
 import org.keycloak.testsuite.crossdc.DC;
-import java.io.NotSerializableException;
-import java.lang.management.ManagementFactory;
-import java.util.Objects;
-import java.util.concurrent.TimeUnit;
-import org.apache.commons.lang3.reflect.FieldUtils;
-import org.jboss.arquillian.core.spi.Validate;
-import org.jboss.logging.Logger;
 
 /**
  *
@@ -81,8 +81,6 @@ public class CacheStatisticsControllerEnricher implements TestEnricher {
     }
 
     private InfinispanStatistics getInfinispanCacheStatistics(JmxInfinispanCacheStatistics annotation) throws MalformedObjectNameException, IOException, MalformedURLException {
-        MBeanServerConnection mbsc = getJmxServerConnection(annotation);
-
         ObjectName mbeanName = new ObjectName(String.format(
           "%s:type=%s,name=\"%s(%s)\",manager=\"%s\",component=%s",
           annotation.domain().isEmpty() ? getDefaultDomain(annotation.dc().getDcIndex(), annotation.dcNodeIndex()) : InfinispanConnectionProvider.JMX_DOMAIN,
@@ -93,7 +91,7 @@ public class CacheStatisticsControllerEnricher implements TestEnricher {
           annotation.component()
         ));
 
-        InfinispanStatistics value = new InfinispanCacheStatisticsImpl(mbsc, mbeanName);
+        InfinispanStatistics value = new InfinispanCacheStatisticsImpl(getJmxServerConnection(annotation), mbeanName);
 
         if (annotation.domain().isEmpty()) {
             try {
@@ -101,7 +99,7 @@ public class CacheStatisticsControllerEnricher implements TestEnricher {
             } catch (RuntimeException ex) {
                 if (annotation.dc() != DC.UNDEFINED && annotation.dcNodeIndex() != -1
                    && suiteContext.get().getAuthServerBackendsInfo(annotation.dc().getDcIndex()).get(annotation.dcNodeIndex()).isStarted()) {
-                    LOG.warn("Could not reset statistics for " + mbeanName);
+                    LOG.warn("Could not reset statistics for " + mbeanName + ". The reason is: \"" + ex.getMessage() + "\"");
                 }
             }
         }
@@ -110,8 +108,6 @@ public class CacheStatisticsControllerEnricher implements TestEnricher {
     }
 
     private InfinispanStatistics getJGroupsChannelStatistics(JmxInfinispanChannelStatistics annotation) throws MalformedObjectNameException, IOException, MalformedURLException {
-        MBeanServerConnection mbsc = getJmxServerConnection(annotation);
-
         ObjectName mbeanName = new ObjectName(String.format(
           "%s:type=%s,cluster=\"%s\"",
           annotation.domain().isEmpty() ? getDefaultDomain(annotation.dc().getDcIndex(), annotation.dcNodeIndex()) : InfinispanConnectionProvider.JMX_DOMAIN,
@@ -119,7 +115,7 @@ public class CacheStatisticsControllerEnricher implements TestEnricher {
           annotation.cluster()
         ));
 
-        InfinispanStatistics value = new InfinispanChannelStatisticsImpl(mbsc, mbeanName);
+        InfinispanStatistics value = new InfinispanChannelStatisticsImpl(getJmxServerConnection(annotation), mbeanName);
 
         if (annotation.domain().isEmpty()) {
             try {
@@ -127,7 +123,7 @@ public class CacheStatisticsControllerEnricher implements TestEnricher {
             } catch (RuntimeException ex) {
                 if (annotation.dc() != DC.UNDEFINED && annotation.dcNodeIndex() != -1
                    && suiteContext.get().getAuthServerBackendsInfo(annotation.dc().getDcIndex()).get(annotation.dcNodeIndex()).isStarted()) {
-                    LOG.warn("Could not reset statistics for " + mbeanName);
+                    LOG.warn("Could not reset statistics for " + mbeanName + ". The reason is: \"" + ex.getMessage() + "\"");
                 }
             }
         }
@@ -162,12 +158,20 @@ public class CacheStatisticsControllerEnricher implements TestEnricher {
 
     private String getDefaultDomain(int dcIndex, int dcNodeIndex) {
         if (dcIndex != -1 && dcNodeIndex != -1) {
+            if (Boolean.parseBoolean(System.getProperty("auth.server.jboss.crossdc"))) {
+                //backend-jboss-server
+                return "org.wildfly.clustering.infinispan";
+            }
+            
+            //backend-undertow-server
             return InfinispanConnectionProvider.JMX_DOMAIN + "-" + suiteContext.get().getAuthServerBackendsInfo(dcIndex).get(dcNodeIndex).getQualifier();
         }
+        
+        //cache-server
         return InfinispanConnectionProvider.JMX_DOMAIN;
     }
 
-    private MBeanServerConnection getJmxServerConnection(JmxInfinispanCacheStatistics annotation) throws MalformedURLException, IOException {
+    private Supplier<MBeanServerConnection> getJmxServerConnection(JmxInfinispanCacheStatistics annotation) throws MalformedURLException {
         final String host;
         final int port;
 
@@ -175,7 +179,7 @@ public class CacheStatisticsControllerEnricher implements TestEnricher {
             ContainerInfo node = suiteContext.get().getAuthServerBackendsInfo(annotation.dc().getDcIndex()).get(annotation.dcNodeIndex());
             Container container = node.getArquillianContainer();
             if (container.getDeployableContainer() instanceof KeycloakOnUndertow) {
-                return ManagementFactory.getPlatformMBeanServer();
+                return () -> ManagementFactory.getPlatformMBeanServer();
             }
             host = "localhost";
             port = container.getContainerConfiguration().getContainerProperties().containsKey("managementPort")
@@ -195,13 +199,18 @@ public class CacheStatisticsControllerEnricher implements TestEnricher {
               : annotation.managementPort();
         }
 
-        JMXServiceURL url = new JMXServiceURL("service:jmx:remote+http://" + host + ":" + port);
-        JMXConnector jmxc = jmxConnectorRegistry.get().getConnection(url);
 
-        return jmxc.getMBeanServerConnection();
+        JMXServiceURL url = new JMXServiceURL("service:jmx:remote+http://" + host + ":" + port);
+        return () -> {
+            try {
+                return jmxConnectorRegistry.get().getConnection(url).getMBeanServerConnection();
+            } catch (IOException ex) {
+                throw new RuntimeException(ex);
+            }
+        };
     }
 
-    private MBeanServerConnection getJmxServerConnection(JmxInfinispanChannelStatistics annotation) throws MalformedURLException, IOException {
+    private Supplier<MBeanServerConnection> getJmxServerConnection(JmxInfinispanChannelStatistics annotation) throws MalformedURLException {
         final String host;
         final int port;
 
@@ -209,7 +218,7 @@ public class CacheStatisticsControllerEnricher implements TestEnricher {
             ContainerInfo node = suiteContext.get().getAuthServerBackendsInfo(annotation.dc().getDcIndex()).get(annotation.dcNodeIndex());
             Container container = node.getArquillianContainer();
             if (container.getDeployableContainer() instanceof KeycloakOnUndertow) {
-                return ManagementFactory.getPlatformMBeanServer();
+                return () -> ManagementFactory.getPlatformMBeanServer();
             }
             host = "localhost";
             port = container.getContainerConfiguration().getContainerProperties().containsKey("managementPort")
@@ -229,46 +238,51 @@ public class CacheStatisticsControllerEnricher implements TestEnricher {
               : annotation.managementPort();
         }
 
-        String jmxUrl = "service:jmx:remote+http://" + host + ":" + port;
-        LOG.infof("JMX Service URL: %s", jmxUrl);
-
-        JMXServiceURL url = new JMXServiceURL(jmxUrl);
-        JMXConnector jmxc = jmxConnectorRegistry.get().getConnection(url);
-
-        return jmxc.getMBeanServerConnection();
+        JMXServiceURL url = new JMXServiceURL("service:jmx:remote+http://" + host + ":" + port);
+        return () -> {
+            try {
+                return jmxConnectorRegistry.get().getConnection(url).getMBeanServerConnection();
+            } catch (IOException ex) {
+                throw new RuntimeException(ex);
+            }
+        };
     }
 
     private static abstract class CacheStatisticsImpl implements InfinispanStatistics {
 
-        protected final MBeanServerConnection mbsc;
+        private final Supplier<MBeanServerConnection> mbscCreateor;
         private final ObjectName mbeanNameTemplate;
         private ObjectName mbeanName;
 
-        public CacheStatisticsImpl(MBeanServerConnection mbsc, ObjectName mbeanNameTemplate) {
-            this.mbsc = mbsc;
+        public CacheStatisticsImpl(Supplier<MBeanServerConnection> mbscCreateor, ObjectName mbeanNameTemplate) {
+            this.mbscCreateor = mbscCreateor;
             this.mbeanNameTemplate = mbeanNameTemplate;
         }
 
+        protected MBeanServerConnection getConnection() {
+            return mbscCreateor.get();
+        }
+        
         @Override
         public boolean exists() {
             try {
                 getMbeanName();
                 return true;
-            } catch (Exception ex) {
+            } catch (IOException | RuntimeException ex) {
                 return false;
             }
         }
-
+        
         @Override
         public Map<String, Object> getStatistics() {
             try {
-                MBeanInfo mBeanInfo = mbsc.getMBeanInfo(getMbeanName());
+                MBeanInfo mBeanInfo = getConnection().getMBeanInfo(getMbeanName());
                 String[] statAttrs = Arrays.asList(mBeanInfo.getAttributes()).stream()
                   .filter(MBeanAttributeInfo::isReadable)
                   .map(MBeanAttributeInfo::getName)
                   .collect(Collectors.toList())
                   .toArray(new String[] {});
-                return mbsc.getAttributes(getMbeanName(), statAttrs)
+                return getConnection().getAttributes(getMbeanName(), statAttrs)
                   .asList()
                   .stream()
                   .collect(Collectors.toMap(Attribute::getName, Attribute::getValue));
@@ -279,7 +293,7 @@ public class CacheStatisticsControllerEnricher implements TestEnricher {
 
         protected ObjectName getMbeanName() throws IOException, RuntimeException {
             if (this.mbeanName == null) {
-                Set<ObjectName> queryNames = mbsc.queryNames(mbeanNameTemplate, null);
+                Set<ObjectName> queryNames = getConnection().queryNames(mbeanNameTemplate, null);
                 if (queryNames.isEmpty()) {
                     throw new RuntimeException("No MBean of template " + mbeanNameTemplate + " found at JMX server");
                 }
@@ -292,7 +306,7 @@ public class CacheStatisticsControllerEnricher implements TestEnricher {
         @Override
         public Comparable getSingleStatistics(String statisticsName) {
             try {
-                return (Comparable) mbsc.getAttribute(getMbeanName(), statisticsName);
+                return (Comparable) getConnection().getAttribute(getMbeanName(), statisticsName);
             } catch (IOException | InstanceNotFoundException | MBeanException | ReflectionException | AttributeNotFoundException ex) {
                 throw new RuntimeException(ex);
             }
@@ -305,7 +319,7 @@ public class CacheStatisticsControllerEnricher implements TestEnricher {
                 try {
                     getMbeanName();
                     if (! isAvailable()) throw new RuntimeException("Not available");
-                } catch (Exception ex) {
+                } catch (IOException | RuntimeException ex) {
                     throw new RuntimeException("Timed out while waiting for " + mbeanNameTemplate + " to become available", ex);
                 }
             }, 1 + (int) timeInMillis / 100, 100);
@@ -316,14 +330,14 @@ public class CacheStatisticsControllerEnricher implements TestEnricher {
 
     private static class InfinispanCacheStatisticsImpl extends CacheStatisticsImpl {
 
-        public InfinispanCacheStatisticsImpl(MBeanServerConnection mbsc, ObjectName mbeanName) {
-            super(mbsc, mbeanName);
+        public InfinispanCacheStatisticsImpl(Supplier<MBeanServerConnection> mbscCreator, ObjectName mbeanName) {
+            super(mbscCreator, mbeanName);
         }
 
         @Override
         public void reset() {
             try {
-                mbsc.invoke(getMbeanName(), "resetStatistics", new Object[] {}, new String[] {});
+                getConnection().invoke(getMbeanName(), "resetStatistics", new Object[] {}, new String[] {});
             } catch (IOException | InstanceNotFoundException | MBeanException | ReflectionException ex) {
                 throw new RuntimeException(ex);
             }
@@ -337,14 +351,14 @@ public class CacheStatisticsControllerEnricher implements TestEnricher {
 
     private static class InfinispanChannelStatisticsImpl extends CacheStatisticsImpl {
 
-        public InfinispanChannelStatisticsImpl(MBeanServerConnection mbsc, ObjectName mbeanName) {
-            super(mbsc, mbeanName);
+        public InfinispanChannelStatisticsImpl(Supplier<MBeanServerConnection> mbscCreator, ObjectName mbeanName) {
+            super(mbscCreator, mbeanName);
         }
 
         @Override
         public void reset() {
             try {
-                mbsc.invoke(getMbeanName(), "resetStats", new Object[] {}, new String[] {});
+                getConnection().invoke(getMbeanName(), "resetStats", new Object[] {}, new String[] {});
             } catch (NotSerializableException ex) {
                 // Ignore return value not serializable, the invocation has already done its job
             } catch (IOException | InstanceNotFoundException | MBeanException | ReflectionException ex) {
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/DeploymentTargetModifier.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/DeploymentTargetModifier.java
index f7fc8cf..339ded7 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/DeploymentTargetModifier.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/DeploymentTargetModifier.java
@@ -22,6 +22,7 @@ import org.jboss.arquillian.container.spi.client.deployment.TargetDescription;
 import org.jboss.arquillian.container.test.impl.client.deployment.AnnotationDeploymentScenarioGenerator;
 import org.jboss.arquillian.test.spi.TestClass;
 import org.jboss.logging.Logger;
+import org.keycloak.common.util.StringPropertyReplacer;
 
 import java.util.List;
 
@@ -73,12 +74,23 @@ public class DeploymentTargetModifier extends AnnotationDeploymentScenarioGenera
             if (deployment.getTarget() != null) {
                 String containerQualifier = deployment.getTarget().getName();
                 if (AUTH_SERVER_CURRENT.equals(containerQualifier)) {
-                    String authServerQualifier = AuthServerTestEnricher.AUTH_SERVER_CONTAINER;
-                    log.infof("Setting target container for deployment %s.%s: %s", testClass.getName(), deployment.getName(), authServerQualifier);
-                    deployment.setTarget(new TargetDescription(authServerQualifier));
+                    String newAuthServerQualifier = AuthServerTestEnricher.AUTH_SERVER_CONTAINER;
+                    updateAuthServerQualifier(deployment, testClass, newAuthServerQualifier);
+                } else {
+                    String newAuthServerQualifier = StringPropertyReplacer.replaceProperties(containerQualifier);
+                    if (!newAuthServerQualifier.equals(containerQualifier)) {
+                        updateAuthServerQualifier(deployment, testClass, newAuthServerQualifier);
+                    }
                 }
+
+
             }
         }
     }
 
+    private void updateAuthServerQualifier(DeploymentDescription deployment, TestClass testClass, String newAuthServerQualifier) {
+        log.infof("Setting target container for deployment %s.%s: %s", testClass.getName(), deployment.getName(), newAuthServerQualifier);
+        deployment.setTarget(new TargetDescription(newAuthServerQualifier));
+    }
+
 }
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/jmx/JmxConnectorRegistryCreator.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/jmx/JmxConnectorRegistryCreator.java
index 50c9b96..e5d1d2a 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/jmx/JmxConnectorRegistryCreator.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/jmx/JmxConnectorRegistryCreator.java
@@ -27,6 +27,7 @@ import org.jboss.arquillian.core.api.annotation.ApplicationScoped;
 import org.jboss.arquillian.core.api.annotation.Inject;
 import org.jboss.arquillian.core.api.annotation.Observes;
 import org.jboss.arquillian.test.spi.event.suite.BeforeSuite;
+import org.jboss.logging.Logger;
 
 /**
  *
@@ -34,6 +35,8 @@ import org.jboss.arquillian.test.spi.event.suite.BeforeSuite;
  */
 public class JmxConnectorRegistryCreator {
 
+    private final Logger log = Logger.getLogger(JmxConnectorRegistryCreator.class);
+            
     @Inject
     @ApplicationScoped
     private InstanceProducer<JmxConnectorRegistry> connectorRegistry;
@@ -46,6 +49,7 @@ public class JmxConnectorRegistryCreator {
 
                 @Override
                 public JMXConnector getConnection(JMXServiceURL url) {
+                    
                     JMXConnector res = connectors.get(url);
                     if (res == null) {
                         try {
@@ -55,7 +59,10 @@ public class JmxConnectorRegistryCreator {
                                 res = conn;
                             }
                             res.connect();
+                            log.infof("Connected to JMX Service URL: %s", url);
                         } catch (IOException ex) {
+                            //remove conn from connectors in case something goes wrong. The connection will be established on-demand
+                            connectors.remove(url, res);
                             throw new RuntimeException("Could not instantiate JMX connector for " + url, ex);
                         }
                     }
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/KeycloakTestingClient.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/KeycloakTestingClient.java
index 533dfbb..7c61206 100755
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/KeycloakTestingClient.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/KeycloakTestingClient.java
@@ -90,14 +90,14 @@ public class KeycloakTestingClient {
 
         public <T> T fetch(FetchOnServer function, Class<T> clazz) throws RunOnServerException {
             try {
-                String s = fetch(function);
+                String s = fetchString(function);
                 return JsonSerialization.readValue(s, clazz);
             } catch (Exception e) {
                 throw new RuntimeException(e);
             }
         }
 
-        public String fetch(FetchOnServer function) throws RunOnServerException {
+        public String fetchString(FetchOnServer function) throws RunOnServerException {
             String encoded = SerializationUtil.encode(function);
 
             String result = testing(realm != null ? realm : "master").runOnServer(encoded);
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/social/TwitterLoginPage.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/social/TwitterLoginPage.java
index 9806b27..c052b9b 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/social/TwitterLoginPage.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/social/TwitterLoginPage.java
@@ -17,6 +17,7 @@
 
 package org.keycloak.testsuite.pages.social;
 
+import org.openqa.selenium.NoSuchElementException;
 import org.openqa.selenium.WebElement;
 import org.openqa.selenium.support.FindBy;
 
@@ -35,9 +36,15 @@ public class TwitterLoginPage extends AbstractSocialLoginPage {
 
     @Override
     public void login(String user, String password) {
-        usernameInput.clear();
-        usernameInput.sendKeys(user);
-        passwordInput.sendKeys(password);
-        loginButton.click();
+        try {
+            usernameInput.clear();
+            usernameInput.sendKeys(user);
+            passwordInput.sendKeys(password);
+        }
+        catch (NoSuchElementException e) { // at some conditions we are already logged in and just need to confirm it
+        }
+        finally {
+            loginButton.click();
+        }
     }
 }
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/DroneUtils.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/DroneUtils.java
index 8efb968..729c685 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/DroneUtils.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/DroneUtils.java
@@ -20,11 +20,33 @@ package org.keycloak.testsuite.util;
 import org.jboss.arquillian.graphene.context.GrapheneContext;
 import org.openqa.selenium.WebDriver;
 
+import java.util.LinkedList;
+import java.util.Queue;
+import java.util.Stack;
+
 /**
  * @author Vaclav Muzikar <vmuzikar@redhat.com>
  */
 public final class DroneUtils {
+    private static Queue<WebDriver> driverQueue = new LinkedList<>();
+
     public static WebDriver getCurrentDriver() {
-        return GrapheneContext.lastContext().getWebDriver();
+        if (driverQueue.isEmpty()) {
+            return GrapheneContext.lastContext().getWebDriver();
+        }
+
+        return driverQueue.peek();
+    }
+
+    public static void addWebDriver(WebDriver driver) {
+        driverQueue.add(driver);
+    }
+
+    public static void removeWebDriver() {
+        driverQueue.poll();
+    }
+
+    public static void resetQueue() {
+        driverQueue = new LinkedList<>();
     }
 }
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/AbstractKeycloakTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/AbstractKeycloakTest.java
index 9b3e1f9..dddeba7 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/AbstractKeycloakTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/AbstractKeycloakTest.java
@@ -64,6 +64,7 @@ import org.keycloak.testsuite.auth.page.login.OIDCLogin;
 import org.keycloak.testsuite.auth.page.login.UpdatePassword;
 import org.keycloak.testsuite.client.KeycloakTestingClient;
 import org.keycloak.testsuite.util.AdminClientUtil;
+import org.keycloak.testsuite.util.DroneUtils;
 import org.keycloak.testsuite.util.OAuthClient;
 import org.keycloak.testsuite.util.TestCleanup;
 import org.keycloak.testsuite.util.TestEventsLogger;
@@ -213,6 +214,9 @@ public abstract class AbstractKeycloakTest {
             }
             testContext.getCleanups().clear();
         }
+
+        // Remove all browsers from queue
+        DroneUtils.resetQueue();
     }
 
     protected TestCleanup getCleanup(String realmName) {
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/AccountFormServiceTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/AccountFormServiceTest.java
index 2c6b772..7fa6a3d 100755
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/AccountFormServiceTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/AccountFormServiceTest.java
@@ -208,6 +208,17 @@ public class AccountFormServiceTest extends AbstractTestRealmKeycloakTest {
     }
 
     @Test
+    public void referrerEscaped() {
+        profilePage.open();
+        loginPage.login("test-user@localhost", "password");
+
+        driver.navigate().to(profilePage.getPath() + "?referrer=test-app&referrer_uri=http://localhost:8180/auth/realms/master/app/auth/test%2Ffkrenu%22%3E%3Cscript%3Ealert%281%29%3C%2fscript%3E");
+        Assert.assertTrue(profilePage.isCurrent());
+
+        Assert.assertFalse(driver.getPageSource().contains("<script>alert"));
+    }
+
+    @Test
     public void changePassword() {
         changePasswordPage.open();
         loginPage.login("test-user@localhost", "password");
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/AbstractFuseAdminAdapterTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/AbstractFuseAdminAdapterTest.java
index ef5d835..e507dde 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/AbstractFuseAdminAdapterTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/AbstractFuseAdminAdapterTest.java
@@ -78,7 +78,7 @@ public abstract class AbstractFuseAdminAdapterTest extends AbstractExampleAdapte
         assertCurrentUrlDoesntStartWith(hawtioPage);
 
         testRealmLoginPage.form().login("root", "password");
-        assertCurrentUrlStartsWith(hawtioPage.getDriver(), hawtioPage.toString() + "/welcome");
+        assertCurrentUrlStartsWith(hawtioPage.toString() + "/welcome", hawtioPage.getDriver());
         hawtioPage.logout();
         assertCurrentUrlStartsWith(testRealmLoginPage);
         
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/AbstractHawtioAdapterTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/AbstractHawtioAdapterTest.java
index c05aac3..c430b46 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/AbstractHawtioAdapterTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/AbstractHawtioAdapterTest.java
@@ -41,7 +41,7 @@ public abstract class AbstractHawtioAdapterTest extends AbstractExampleAdapterTe
         testRealmLoginPage.form().login("root", "password");
 
         waitUntilElement(By.xpath("//body")).is().present();
-        assertCurrentUrlStartsWith(hawtioPage.getDriver(), hawtioPage.toString() + "/welcome");
+        assertCurrentUrlStartsWith(hawtioPage.toString() + "/welcome", hawtioPage.getDriver());
 
         hawtioPage.logout();
         pause(1000);
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractDemoServletsAdapterTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractDemoServletsAdapterTest.java
index 721c89e..36ac062 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractDemoServletsAdapterTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractDemoServletsAdapterTest.java
@@ -194,7 +194,7 @@ public abstract class AbstractDemoServletsAdapterTest extends AbstractServletsAd
 
         assertCurrentUrlStartsWithLoginUrlOf(testRealmPage);
         testRealmLoginPage.form().login("bburke@redhat.com", "password");
-        assertCurrentUrlEquals(driver, inputPortal + "/secured/post");
+        assertCurrentUrlEquals(inputPortal + "/secured/post");
         waitForPageToLoad();
         String pageSource = driver.getPageSource();
         assertThat(pageSource, containsString("parameter=hello"));
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractOIDCPublicKeyRotationAdapterTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractOIDCPublicKeyRotationAdapterTest.java
index c31b9c5..3c959a1 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractOIDCPublicKeyRotationAdapterTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractOIDCPublicKeyRotationAdapterTest.java
@@ -127,7 +127,7 @@ public abstract class AbstractOIDCPublicKeyRotationAdapterTest extends AbstractS
         testRealmLoginPage.form().waitForUsernameInputPresent();
         assertCurrentUrlStartsWithLoginUrlOf(testRealmPage);
         testRealmLoginPage.form().login("bburke@redhat.com", "password");
-        URLAssert.assertCurrentUrlStartsWith(driver, tokenMinTTLPage.getInjectedUrl().toString());
+        URLAssert.assertCurrentUrlStartsWith(tokenMinTTLPage.getInjectedUrl().toString());
         Assert.assertNull(tokenMinTTLPage.getAccessToken());
 
         driver.navigate().to(logoutUri);
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractSessionServletAdapterTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractSessionServletAdapterTest.java
index a95a00e..65124e4 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractSessionServletAdapterTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractSessionServletAdapterTest.java
@@ -37,9 +37,8 @@ import org.keycloak.testsuite.util.SecondBrowser;
 import org.openqa.selenium.By;
 import org.openqa.selenium.WebDriver;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.*;
 import static org.keycloak.testsuite.auth.page.AuthRealm.DEMO;
 import static org.keycloak.testsuite.util.URLAssert.assertCurrentUrlEquals;
 import static org.keycloak.testsuite.util.URLAssert.assertCurrentUrlStartsWithLoginUrlOf;
@@ -84,13 +83,13 @@ public abstract class AbstractSessionServletAdapterTest extends AbstractServlets
 
         // cannot pass to loginAndCheckSession becayse loginPage is not working together with driver2, therefore copypasta
         driver2.navigate().to(sessionPortalPage.toString());
-        assertCurrentUrlStartsWithLoginUrlOf(driver2, testRealmPage);
+        assertCurrentUrlStartsWithLoginUrlOf(testRealmPage, driver2);
         driver2.findElement(By.id("username")).sendKeys("bburke@redhat.com");
         driver2.findElement(By.id("password")).sendKeys("password");
         driver2.findElement(By.id("password")).submit();
-        assertCurrentUrlEquals(driver2, sessionPortalPage);
+        assertCurrentUrlEquals(sessionPortalPage, driver2);
         String pageSource = driver2.getPageSource();
-        assertTrue(pageSource.contains("Counter=1"));
+        assertThat(pageSource, containsString("Counter=1"));
         // Counter increased now
         driver2.navigate().to(sessionPortalPage.toString());
         pageSource = driver2.getPageSource();
@@ -108,12 +107,12 @@ public abstract class AbstractSessionServletAdapterTest extends AbstractServlets
 
         // Assert that I am still logged in browser2 and same session is still preserved
         driver2.navigate().to(sessionPortalPage.toString());
-        assertCurrentUrlEquals(driver2, sessionPortalPage);
+        assertCurrentUrlEquals(sessionPortalPage, driver2);
         pageSource = driver2.getPageSource();
-        assertTrue(pageSource.contains("Counter=3"));
+        assertThat(pageSource, containsString("Counter=3"));
 
         driver2.navigate().to(logoutUri);
-        assertCurrentUrlStartsWithLoginUrlOf(driver2, testRealmPage);
+        assertCurrentUrlStartsWithLoginUrlOf(testRealmPage, driver2);
 
     }
 
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/concurrency/ConcurrentLoginTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/concurrency/ConcurrentLoginTest.java
index ee38fae..219d7a2 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/concurrency/ConcurrentLoginTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/concurrency/ConcurrentLoginTest.java
@@ -21,8 +21,8 @@ import java.io.IOException;
 import java.io.UnsupportedEncodingException;
 import java.net.URI;
 import java.net.URISyntaxException;
+import java.nio.charset.Charset;
 import java.util.ArrayList;
-import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import javax.ws.rs.core.Response;
@@ -58,10 +58,12 @@ import java.util.Arrays;
 import java.util.LinkedHashMap;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.concurrent.atomic.AtomicReference;
+import java.util.stream.Collectors;
 import org.apache.http.client.CookieStore;
 import org.apache.http.impl.client.BasicCookieStore;
 import org.hamcrest.Matchers;
 
+import static org.hamcrest.Matchers.containsString;
 
 
 /**
@@ -126,8 +128,7 @@ public class ConcurrentLoginTest extends AbstractConcurrencyTest {
         CookieStore cookieStore = new BasicCookieStore();
         context.setCookieStore(cookieStore);
         HttpUriRequest request = handleLogin(getPageContent(oauth.getLoginFormUrl(), httpClient, context), userName, password);
-        log.debug("Executing login request");
-        Assert.assertTrue(parseAndCloseResponse(httpClient.execute(request, context)).contains("<title>AUTH_RESPONSE</title>"));
+        Assert.assertThat(parseAndCloseResponse(httpClient.execute(request, context)), containsString("<title>AUTH_RESPONSE</title>"));
         return context;
     }
 
@@ -306,12 +307,8 @@ public class ConcurrentLoginTest extends AbstractConcurrencyTest {
     }
 
     private static Map<String, String> getQueryFromUrl(String url) throws URISyntaxException {
-        Map<String, String> m = new HashMap<>();
-        List<NameValuePair> pairs = URLEncodedUtils.parse(new URI(url), "UTF-8");
-        for (NameValuePair p : pairs) {
-            m.put(p.getName(), p.getValue());
-        }
-        return m;
+        return URLEncodedUtils.parse(new URI(url), Charset.forName("UTF-8")).stream()
+                .collect(Collectors.toMap(p -> p.getName(), p -> p.getValue()));
     }
 
 
@@ -411,4 +408,4 @@ public class ConcurrentLoginTest extends AbstractConcurrencyTest {
     }
 
     
-}
\ No newline at end of file
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/SMTPConnectionTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/SMTPConnectionTest.java
index 303cfd6..ac7a947 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/SMTPConnectionTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/SMTPConnectionTest.java
@@ -35,6 +35,7 @@ import java.util.Map;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.fail;
+import static org.keycloak.representations.idm.ComponentRepresentation.SECRET_VALUE;
 import static org.keycloak.util.JsonSerialization.writeValueAsPrettyString;
 
 /**
@@ -60,6 +61,11 @@ public class SMTPConnectionTest extends AbstractKeycloakTest {
 
     private String settings(String host, String port, String from, String auth, String ssl, String starttls,
                             String username, String password) throws Exception {
+        Map<String, String> config = smtpMap(host, port, from, auth, ssl, starttls, username, password);
+        return writeValueAsPrettyString(config);
+    }
+
+    private Map<String, String> smtpMap(String host, String port, String from, String auth, String ssl, String starttls, String username, String password) {
         Map<String, String> config = new HashMap<>();
         config.put("host", host);
         config.put("port", port);
@@ -69,7 +75,7 @@ public class SMTPConnectionTest extends AbstractKeycloakTest {
         config.put("starttls", starttls);
         config.put("user", username);
         config.put("password", password);
-        return writeValueAsPrettyString(config);
+        return config;
     }
 
     @Test
@@ -102,6 +108,26 @@ public class SMTPConnectionTest extends AbstractKeycloakTest {
         assertStatus(response, 204);
     }
 
+    @Test
+    public void testAuthEnabledAndSavedCredentials() throws Exception {
+        RealmRepresentation realmRep = realm.toRepresentation();
+        Map<String, String> oldSmtp = realmRep.getSmtpServer();
+        try {
+            realmRep.setSmtpServer(smtpMap("127.0.0.1", "3025", "auto@keycloak.org", "true", null, null,
+                    "admin@localhost", "admin"));
+            realm.update(realmRep);
+
+            greenMailRule.credentials("admin@localhost", "admin");
+            Response response = realm.testSMTPConnection(settings("127.0.0.1", "3025", "auto@keycloak.org", "true", null, null,
+                    "admin@localhost", SECRET_VALUE));
+            assertStatus(response, 204);
+        } finally {
+            // Revert SMTP back
+            realmRep.setSmtpServer(oldSmtp);
+            realm.update(realmRep);
+        }
+    }
+
     private void assertStatus(Response response, int status) {
         assertEquals(status, response.getStatus());
         response.close();
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/crossdc/AbstractAdminCrossDCTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/crossdc/AbstractAdminCrossDCTest.java
index 27fed71..b318c14 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/crossdc/AbstractAdminCrossDCTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/crossdc/AbstractAdminCrossDCTest.java
@@ -95,8 +95,10 @@ public abstract class AbstractAdminCrossDCTest extends AbstractCrossDCTest {
             T newStat = (T) stats.getSingleStatistics(key);
 
             Matcher<? super T> matcherInstance = matcherOnOldStat.apply(oldStat);
+            
+            log.infof("assertSingleStatistics '%s' : oldStat: %s, newStat: %s", key, oldStat.toString(), newStat.toString());
             assertThat(newStat, matcherInstance);
-        }, 20, 200);
+        }, 50, 200);
     }
 
     protected void assertStatistics(InfinispanStatistics stats, Runnable testedCode, BiConsumer<Map<String, Object>, Map<String, Object>> assertionOnStats) {
@@ -108,7 +110,7 @@ public abstract class AbstractAdminCrossDCTest extends AbstractCrossDCTest {
         Retry.execute(() -> {
             Map<String, Object> newStat = stats.getStatistics();
             assertionOnStats.accept(oldStat, newStat);
-        }, 5, 200);
+        }, 50, 200);
     }
 
 }
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/crossdc/AbstractCrossDCTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/crossdc/AbstractCrossDCTest.java
index d800162..76f6a7e 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/crossdc/AbstractCrossDCTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/crossdc/AbstractCrossDCTest.java
@@ -50,6 +50,8 @@ public abstract class AbstractCrossDCTest extends AbstractTestRealmKeycloakTest 
 
     // Keep the following constants in sync with arquillian
     public static final String QUALIFIER_NODE_BALANCER = "auth-server-balancer-cross-dc";
+    public static final String QUALIFIER_AUTH_SERVER_DC_0_NODE_1 = "auth-server-${node.name}-cross-dc-0_1";
+    public static final String QUALIFIER_AUTH_SERVER_DC_1_NODE_1 = "auth-server-${node.name}-cross-dc-1_1";
 
     @ArquillianResource
     @LoadBalancer(value = QUALIFIER_NODE_BALANCER)
@@ -208,17 +210,19 @@ public abstract class AbstractCrossDCTest extends AbstractTestRealmKeycloakTest 
 
     /**
      * Disables routing requests to the given data center in the load balancer.
-     * @param dcIndex
+     * @param dc
      */
     public void disableDcOnLoadBalancer(DC dc) {
         int dcIndex = dc.ordinal();
         log.infof("Disabling load balancer for dc=%d", dcIndex);
-        this.suiteContext.getDcAuthServerBackendsInfo().get(dcIndex).forEach(c -> loadBalancerCtrl.disableBackendNodeByName(c.getQualifier()));
+        this.suiteContext.getDcAuthServerBackendsInfo().get(dcIndex).forEach(containerInfo -> {
+            loadBalancerCtrl.disableBackendNodeByName(containerInfo.getQualifier());
+        });
     }
 
     /**
      * Enables routing requests to all started nodes to the given data center in the load balancer.
-     * @param dcIndex
+     * @param dc
      */
     public void enableDcOnLoadBalancer(DC dc) {
         int dcIndex = dc.ordinal();
@@ -229,13 +233,15 @@ public abstract class AbstractCrossDCTest extends AbstractTestRealmKeycloakTest 
         } else {
             dcNodes.stream()
               .filter(ContainerInfo::isStarted)
-              .forEach(c -> loadBalancerCtrl.enableBackendNodeByName(c.getQualifier()));
+              .forEach(containerInfo -> {
+                  loadBalancerCtrl.enableBackendNodeByName(containerInfo.getQualifier());
+              });
         }
     }
 
     /**
      * Disables routing requests to the given node within the given data center in the load balancer.
-     * @param dcIndex
+     * @param dc
      * @param nodeIndex
      */
     public void disableLoadBalancerNode(DC dc, int nodeIndex) {
@@ -246,7 +252,7 @@ public abstract class AbstractCrossDCTest extends AbstractTestRealmKeycloakTest 
 
     /**
      * Enables routing requests to the given node within the given data center in the load balancer.
-     * @param dcIndex
+     * @param dc
      * @param nodeIndex
      */
     public void enableLoadBalancerNode(DC dc, int nodeIndex) {
@@ -264,7 +270,7 @@ public abstract class AbstractCrossDCTest extends AbstractTestRealmKeycloakTest 
 
     /**
      * Starts a manually-controlled backend auth-server node in cross-DC scenario.
-     * @param dcIndex
+     * @param dc
      * @param nodeIndex
      * @return Started instance descriptor.
      */
@@ -275,6 +281,8 @@ public abstract class AbstractCrossDCTest extends AbstractTestRealmKeycloakTest 
         assertThat((Integer) nodeIndex, lessThan(dcNodes.size()));
         ContainerInfo dcNode = dcNodes.get(nodeIndex);
         assertTrue("Node " + dcNode.getQualifier() + " has to be controlled manually", dcNode.isManual());
+        
+        log.infof("Starting backend node: %s (dcIndex: %d, nodeIndex: %d)", dcNode.getQualifier(), dcIndex, nodeIndex);
         containerController.start(dcNode.getQualifier());
 
         createRESTClientsForNode(dcNode);
@@ -284,7 +292,7 @@ public abstract class AbstractCrossDCTest extends AbstractTestRealmKeycloakTest 
 
     /**
      * Stops a manually-controlled backend auth-server node in cross-DC scenario.
-     * @param dcIndex
+     * @param dc
      * @param nodeIndex
      * @return Stopped instance descriptor.
      */
@@ -298,13 +306,15 @@ public abstract class AbstractCrossDCTest extends AbstractTestRealmKeycloakTest 
         removeRESTClientsForNode(dcNode);
 
         assertTrue("Node " + dcNode.getQualifier() + " has to be controlled manually", dcNode.isManual());
+        
+        log.infof("Stopping backend node: %s (dcIndex: %d, nodeIndex: %d)", dcNode.getQualifier(), dcIndex, nodeIndex);
         containerController.stop(dcNode.getQualifier());
         return dcNode;
     }
 
     /**
      * Returns stream of all nodes in the given dc that are started manually.
-     * @param dcIndex
+     * @param dc
      * @return
      */
     public Stream<ContainerInfo> getManuallyStartedBackendNodes(DC dc) {
@@ -315,7 +325,7 @@ public abstract class AbstractCrossDCTest extends AbstractTestRealmKeycloakTest 
 
     /**
      * Returns stream of all nodes in the given dc that are started automatically.
-     * @param dcIndex
+     * @param dc
      * @return
      */
     public Stream<ContainerInfo> getAutomaticallyStartedBackendNodes(DC dc) {
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/crossdc/ActionTokenCrossDCTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/crossdc/ActionTokenCrossDCTest.java
index 1a4e079..e16597a 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/crossdc/ActionTokenCrossDCTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/crossdc/ActionTokenCrossDCTest.java
@@ -42,6 +42,8 @@ import org.keycloak.testsuite.arquillian.annotation.JmxInfinispanChannelStatisti
 import org.keycloak.testsuite.arquillian.InfinispanStatistics;
 import org.keycloak.testsuite.arquillian.InfinispanStatistics.Constants;
 import org.keycloak.testsuite.pages.ProceedPage;
+
+import java.util.Map;
 import java.util.concurrent.TimeUnit;
 import org.hamcrest.Matchers;
 import static org.hamcrest.Matchers.greaterThan;
@@ -104,8 +106,6 @@ public class ActionTokenCrossDCTest extends AbstractAdminCrossDCTest {
 
         String link = MailUtils.getPasswordResetEmailLink(message);
 
-        Retry.execute(() -> channelStatisticsCrossDc.reset(), 3, 100);
-
         assertSingleStatistics(cacheDc0Node0Statistics, Constants.STAT_CACHE_NUMBER_OF_ENTRIES,
           () -> driver.navigate().to(link),
           Matchers::is
@@ -115,10 +115,21 @@ public class ActionTokenCrossDCTest extends AbstractAdminCrossDCTest {
         proceedPage.clickProceedLink();
         passwordUpdatePage.assertCurrent();
 
-        // Verify that there was at least one message sent via the channel
-        assertSingleStatistics(channelStatisticsCrossDc, Constants.STAT_CHANNEL_SENT_MESSAGES,
-          () -> passwordUpdatePage.changePassword("new-pass", "new-pass"),
-          old -> greaterThan((Comparable) 0l)
+        // Verify that there was at least one message sent via the channel - Even if we did the change on DC0, the message may be sent either from DC0 or DC1. Seems it depends on the actionTokens key ownership.
+        // In case that it was sent from DC1, we will receive it in DC0.
+        assertStatistics(channelStatisticsCrossDc,
+                () -> {
+                    passwordUpdatePage.changePassword("new-pass", "new-pass");
+                },
+                (Map<String, Object> oldStats, Map<String, Object> newStats) -> {
+                    int oldSent = ((Number) oldStats.get(Constants.STAT_CHANNEL_SENT_MESSAGES)).intValue();
+                    int newSent = ((Number) newStats.get(Constants.STAT_CHANNEL_SENT_MESSAGES)).intValue();
+                    int oldReceived = ((Number) oldStats.get(Constants.STAT_CHANNEL_RECEIVED_MESSAGES)).intValue();
+                    int newReceived = ((Number) newStats.get(Constants.STAT_CHANNEL_RECEIVED_MESSAGES)).intValue();
+
+                    log.infof("oldSent: %d, newSent: %d, oldReceived: %d, newReceived: %d", oldSent, newSent, oldReceived, newReceived);
+                    Assert.assertTrue(newSent - oldSent > 0 || newReceived - oldReceived > 0);
+                }
         );
 
         assertEquals("Your account has been updated.", driver.getTitle());
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/crossdc/BruteForceCrossDCTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/crossdc/BruteForceCrossDCTest.java
index ec572e9..3007cab 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/crossdc/BruteForceCrossDCTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/crossdc/BruteForceCrossDCTest.java
@@ -21,6 +21,9 @@ import java.io.IOException;
 import java.net.URISyntaxException;
 
 import javax.ws.rs.NotFoundException;
+import org.jboss.arquillian.container.test.api.Deployment;
+import org.jboss.arquillian.container.test.api.TargetsContainer;
+import org.jboss.shrinkwrap.api.spec.WebArchive;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -31,9 +34,11 @@ import org.keycloak.models.UserLoginFailureModel;
 import org.keycloak.representations.idm.ClientRepresentation;
 import org.keycloak.representations.idm.RealmRepresentation;
 import org.keycloak.representations.idm.UserRepresentation;
+import org.keycloak.testsuite.AbstractTestRealmKeycloakTest;
 import org.keycloak.testsuite.Assert;
 import org.keycloak.testsuite.Retry;
 import org.keycloak.testsuite.client.KeycloakTestingClient;
+import org.keycloak.testsuite.runonserver.RunOnServerDeployment;
 import org.keycloak.testsuite.util.ClientBuilder;
 import org.keycloak.testsuite.util.OAuthClient;
 import org.keycloak.testsuite.util.RealmBuilder;
@@ -45,7 +50,31 @@ import org.keycloak.testsuite.util.UserBuilder;
 public class BruteForceCrossDCTest extends AbstractAdminCrossDCTest {
 
     private static final String REALM_NAME = "brute-force-test";
-
+    
+    @Deployment(name = "dc0")
+    @TargetsContainer(QUALIFIER_AUTH_SERVER_DC_0_NODE_1)
+    public static WebArchive deployDC0() {
+        return RunOnServerDeployment.create(
+                BruteForceCrossDCTest.class,
+                AbstractAdminCrossDCTest.class,
+                AbstractCrossDCTest.class,
+                AbstractTestRealmKeycloakTest.class,
+                KeycloakTestingClient.class
+        );
+    }
+    
+    @Deployment(name = "dc1")
+    @TargetsContainer(QUALIFIER_AUTH_SERVER_DC_1_NODE_1)
+    public static WebArchive deployDC1() {
+        return RunOnServerDeployment.create(
+                BruteForceCrossDCTest.class,
+                AbstractAdminCrossDCTest.class,
+                AbstractCrossDCTest.class,
+                AbstractTestRealmKeycloakTest.class,
+                KeycloakTestingClient.class
+        );
+    }
+    
     @Before
     public void beforeTest() {
         try {
@@ -93,7 +122,7 @@ public class BruteForceCrossDCTest extends AbstractAdminCrossDCTest {
 
     @Test
     public void testBruteForceWithUserOperations() throws Exception {
-        // Enable 1st DC only
+        // Enable 1st node on each DC only
         enableDcOnLoadBalancer(DC.FIRST);
         enableDcOnLoadBalancer(DC.SECOND);
 
@@ -125,7 +154,7 @@ public class BruteForceCrossDCTest extends AbstractAdminCrossDCTest {
 
     @Test
     public void testBruteForceWithRealmOperations() throws Exception {
-        // Enable 1st DC only
+        // Enable 1st node on each DC only
         enableDcOnLoadBalancer(DC.FIRST);
         enableDcOnLoadBalancer(DC.SECOND);
 
@@ -162,7 +191,7 @@ public class BruteForceCrossDCTest extends AbstractAdminCrossDCTest {
 
     @Test
     public void testDuplicatedPutIfAbsentOperation() throws Exception {
-        // Enable 1st DC only
+        // Enable 1st node on each DC only
         enableDcOnLoadBalancer(DC.FIRST);
         enableDcOnLoadBalancer(DC.SECOND);
 
@@ -219,7 +248,7 @@ public class BruteForceCrossDCTest extends AbstractAdminCrossDCTest {
     }
 
 
-    // TODO Having this working on Wildfly might be a challenge. Maybe require @Deployment with @TargetsContainer descriptor generated at runtime as we don't know the container qualifier at compile time... Maybe workaround by add endpoint to TestingResourceProvider if needed..
+    // resolution on Wildfly: make deployment available on both dc0_1 and dc1_1, see @Deployment methods
     private void addUserLoginFailure(KeycloakTestingClient testingClient) throws URISyntaxException, IOException {
         testingClient.server().run(session -> {
             RealmModel realm = session.realms().getRealmByName(REALM_NAME);
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/crossdc/InvalidationCrossDCTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/crossdc/InvalidationCrossDCTest.java
new file mode 100644
index 0000000..c3819f7
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/crossdc/InvalidationCrossDCTest.java
@@ -0,0 +1,216 @@
+/*
+ * Copyright 2017 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.crossdc;
+
+import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import javax.ws.rs.core.Response;
+
+import org.junit.Test;
+import org.keycloak.admin.client.resource.ClientResource;
+import org.keycloak.admin.client.resource.ResourcesResource;
+import org.keycloak.admin.client.resource.UserResource;
+import org.keycloak.representations.idm.ClientRepresentation;
+import org.keycloak.representations.idm.RealmRepresentation;
+import org.keycloak.representations.idm.UserRepresentation;
+import org.keycloak.representations.idm.authorization.ResourceRepresentation;
+import org.keycloak.testsuite.Assert;
+import org.keycloak.testsuite.Retry;
+import org.keycloak.testsuite.admin.ApiUtil;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class InvalidationCrossDCTest extends AbstractAdminCrossDCTest {
+
+    private static final String REALM_NAME = "test";
+
+    @Test
+    public void realmInvalidationTest() throws Exception {
+        enableDcOnLoadBalancer(DC.FIRST);
+        enableDcOnLoadBalancer(DC.SECOND);
+
+        RealmRepresentation realmDc0 = getAdminClientForStartedNodeInDc(0).realms().realm(REALM_NAME).toRepresentation();
+        RealmRepresentation realmDc1 = getAdminClientForStartedNodeInDc(1).realms().realm(REALM_NAME).toRepresentation();
+
+        // Test same realm on both DCs
+        Assert.assertNull(realmDc0.getDisplayName());
+        Assert.assertTrue(realmDc0.isRegistrationAllowed());
+        Assert.assertNull(realmDc1.getDisplayName());
+        Assert.assertTrue(realmDc1.isRegistrationAllowed());
+
+        // Update realm on DC0
+        realmDc0.setRegistrationAllowed(false);
+        realmDc0.setDisplayName("Cool Realm!");
+        getAdminClientForStartedNodeInDc(0).realms().realm(REALM_NAME).update(realmDc0);
+
+        // Assert updated on both DC0 and DC1 (here retry is needed. We need to wait until invalidation message arrives)
+        realmDc0 = getAdminClientForStartedNodeInDc(0).realms().realm(REALM_NAME).toRepresentation();
+        Assert.assertEquals("Cool Realm!", realmDc0.getDisplayName());
+        Assert.assertFalse(realmDc0.isRegistrationAllowed());
+
+        AtomicInteger i = new AtomicInteger(0);
+        Retry.execute(() -> {
+            i.incrementAndGet();
+            RealmRepresentation realmDcc1 = getAdminClientForStartedNodeInDc(1).realms().realm(REALM_NAME).toRepresentation();
+            Assert.assertEquals("Cool Realm!", realmDcc1.getDisplayName());
+            Assert.assertFalse(realmDcc1.isRegistrationAllowed());
+        }, 50, 50);
+
+        log.infof("realmInvalidationTest: Passed after '%d' iterations", i.get());
+    }
+
+
+    @Test
+    public void clientInvalidationTest() throws Exception {
+        enableDcOnLoadBalancer(DC.FIRST);
+        enableDcOnLoadBalancer(DC.SECOND);
+
+        ClientResource clientResourceDc0 = ApiUtil.findClientByClientId(getAdminClientForStartedNodeInDc(0).realms().realm(REALM_NAME), "named-test-app");
+        ClientResource clientResourceDc1 = ApiUtil.findClientByClientId(getAdminClientForStartedNodeInDc(1).realms().realm(REALM_NAME), "named-test-app");
+        ClientRepresentation clientDc0 = clientResourceDc0.toRepresentation();
+        ClientRepresentation clientDc1 = clientResourceDc1.toRepresentation();
+
+        // Test same client on both DCs
+        Assert.assertEquals("My Named Test App", clientDc0.getName());
+        Assert.assertEquals("My Named Test App", clientDc1.getName());
+
+        // Update client on DC0
+        clientDc0.setName("Changed Test App");
+        clientResourceDc0.update(clientDc0);
+
+        // Assert updated on both DC0 and DC1 (here retry is needed. We need to wait until invalidation message arrives)
+        clientDc0 = clientResourceDc0.toRepresentation();
+        Assert.assertEquals("Changed Test App", clientDc0.getName());
+
+        AtomicInteger i = new AtomicInteger(0);
+        Retry.execute(() -> {
+            i.incrementAndGet();
+            ClientRepresentation clientDcc1 = clientResourceDc1.toRepresentation();
+            Assert.assertEquals("Changed Test App", clientDcc1.getName());
+        }, 50, 50);
+
+        log.infof("clientInvalidationTest: Passed after '%d' iterations", i.get());
+    }
+
+
+    @Test
+    public void clientListInvalidationTest() throws Exception {
+        enableDcOnLoadBalancer(DC.FIRST);
+        enableDcOnLoadBalancer(DC.SECOND);
+
+        List<ClientRepresentation> dc0List = getAdminClientForStartedNodeInDc(0).realms().realm(REALM_NAME).clients().findAll();
+        List<ClientRepresentation> dc1List = getAdminClientForStartedNodeInDc(1).realms().realm(REALM_NAME).clients().findAll();
+
+
+        // Test same clients on both DCs
+        Assert.assertEquals(dc0List.size(), dc1List.size());
+        int initialSize = dc0List.size();
+
+        // Create client on DC0
+        ClientRepresentation rep = new ClientRepresentation();
+        rep.setClientId("some-new-client");
+        rep.setEnabled(true);
+        Response response = getAdminClientForStartedNodeInDc(0).realms().realm(REALM_NAME).clients().create(rep);
+        Assert.assertEquals(201, response.getStatus());
+        response.close();
+
+        // Assert updated on both DC0 and DC1 (here retry is needed. We need to wait until invalidation message arrives)
+        dc0List = getAdminClientForStartedNodeInDc(0).realms().realm(REALM_NAME).clients().findAll();
+        Assert.assertEquals(initialSize + 1, dc0List.size());
+
+        AtomicInteger i = new AtomicInteger(0);
+        Retry.execute(() -> {
+            i.incrementAndGet();
+            List<ClientRepresentation> dc1Listt = getAdminClientForStartedNodeInDc(1).realms().realm(REALM_NAME).clients().findAll();
+            Assert.assertEquals(initialSize + 1, dc1Listt.size());
+        }, 50, 50);
+
+        log.infof("clientListInvalidationTest: Passed after '%d' iterations", i.get());
+    }
+
+
+    @Test
+    public void userInvalidationTest() throws Exception {
+        enableDcOnLoadBalancer(DC.FIRST);
+        enableDcOnLoadBalancer(DC.SECOND);
+
+        UserResource userResourceDc0 = ApiUtil.findUserByUsernameId(getAdminClientForStartedNodeInDc(0).realms().realm(REALM_NAME), "test-user@localhost");
+        UserResource userResourceDc1 = ApiUtil.findUserByUsernameId(getAdminClientForStartedNodeInDc(1).realms().realm(REALM_NAME), "test-user@localhost");
+        UserRepresentation userDc0 = userResourceDc0.toRepresentation();
+        UserRepresentation userDc1 = userResourceDc1.toRepresentation();
+
+        // Test same user on both DCs
+        Assert.assertEquals("Tom", userDc0.getFirstName());
+        Assert.assertEquals("Tom", userDc1.getFirstName());
+
+        // Update user on DC0
+        userDc0.setFirstName("Brad");
+        userResourceDc0.update(userDc0);
+
+        // Assert updated on both DC0 and DC1 (here retry is needed. We need to wait until invalidation message arrives)
+        userDc0 = userResourceDc0.toRepresentation();
+        Assert.assertEquals("Brad", userDc0.getFirstName());
+
+        AtomicInteger i = new AtomicInteger(0);
+        Retry.execute(() -> {
+            i.incrementAndGet();
+            UserRepresentation userDcc1 = userResourceDc1.toRepresentation();
+            Assert.assertEquals("Brad", userDcc1.getFirstName());
+        }, 50, 50);
+
+        log.infof("userInvalidationTest: Passed after '%d' iterations", i.get());
+    }
+
+
+    @Test
+    public void authzResourceInvalidationTest() throws Exception {
+        enableDcOnLoadBalancer(DC.FIRST);
+        enableDcOnLoadBalancer(DC.SECOND);
+
+
+        ResourcesResource resourcesDc0Resource = ApiUtil.findClientByClientId(getAdminClientForStartedNodeInDc(0).realms().realm(REALM_NAME), "test-app-authz").authorization().resources();
+        ResourcesResource resourcesDc1Resource = ApiUtil.findClientByClientId(getAdminClientForStartedNodeInDc(1).realms().realm(REALM_NAME), "test-app-authz").authorization().resources();
+        ResourceRepresentation resDc0 = resourcesDc0Resource.findByName("Premium Resource").get(0);
+        ResourceRepresentation resDc1 = resourcesDc1Resource.findByName("Premium Resource").get(0);
+
+        // Test same resource on both DCs
+        Assert.assertEquals("/protected/premium/*", resDc0.getUri());
+        Assert.assertEquals("/protected/premium/*", resDc1.getUri());
+
+        // Update resource on DC0
+        resDc0.setUri("/protected/ultra/premium/*");
+        resourcesDc0Resource.resource(resDc0.getId()).update(resDc0);
+
+        // Assert updated on both DC0 and DC1 (here retry is needed. We need to wait until invalidation message arrives)
+        resDc0 = resourcesDc0Resource.findByName("Premium Resource").get(0);
+        Assert.assertEquals("/protected/ultra/premium/*", resDc0.getUri());
+
+        AtomicInteger i = new AtomicInteger(0);
+        Retry.execute(() -> {
+            i.incrementAndGet();
+            ResourceRepresentation ressDc1 = resourcesDc1Resource.findByName("Premium Resource").get(0);
+            Assert.assertEquals("/protected/ultra/premium/*", ressDc1.getUri());
+        }, 50, 50);
+
+        log.infof("authzResourceInvalidationTest: Passed after '%d' iterations", i.get());
+    }
+
+
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/BruteForceTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/BruteForceTest.java
index 884762b..6741577 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/BruteForceTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/BruteForceTest.java
@@ -24,6 +24,7 @@ import org.junit.Rule;
 import org.junit.Test;
 import org.keycloak.events.Details;
 import org.keycloak.events.Errors;
+import org.keycloak.events.EventType;
 import org.keycloak.models.Constants;
 import org.keycloak.models.utils.TimeBasedOTP;
 import org.keycloak.representations.idm.CredentialRepresentation;
@@ -35,6 +36,7 @@ import org.keycloak.testsuite.AbstractTestRealmKeycloakTest;
 import org.keycloak.testsuite.pages.AppPage;
 import org.keycloak.testsuite.pages.AppPage.RequestType;
 import org.keycloak.testsuite.pages.LoginPage;
+import org.keycloak.testsuite.pages.LoginPasswordResetPage;
 import org.keycloak.testsuite.pages.LoginTotpPage;
 import org.keycloak.testsuite.pages.RegisterPage;
 import org.keycloak.testsuite.util.GreenMailRule;
@@ -44,12 +46,16 @@ import org.keycloak.testsuite.util.UserBuilder;
 
 import java.net.MalformedURLException;
 
+import static org.junit.Assert.assertEquals;
+
 /**
  * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
  * @author Stan Silvert ssilvert@redhat.com (C) 2016 Red Hat Inc.
  */
 public class BruteForceTest extends AbstractTestRealmKeycloakTest {
 
+    private static String userId;
+
     @Override
     public void configureTestRealm(RealmRepresentation testRealm) {
         UserRepresentation user = RealmRepUtil.findUser(testRealm, "test-user@localhost");
@@ -62,6 +68,8 @@ public class BruteForceTest extends AbstractTestRealmKeycloakTest {
         testRealm.setBruteForceProtected(true);
         testRealm.setFailureFactor(2);
 
+        userId = user.getId();
+
         RealmRepUtil.findClientByClientId(testRealm, "test-app").setDirectAccessGrantsEnabled(true);
         testRealm.getUsers().add(UserBuilder.create().username("user2").email("user2@localhost").password("password").build());
     }
@@ -74,7 +82,6 @@ public class BruteForceTest extends AbstractTestRealmKeycloakTest {
     @After
     public void slowItDown() throws Exception {
         Thread.sleep(100);
-
     }
 
 
@@ -91,6 +98,9 @@ public class BruteForceTest extends AbstractTestRealmKeycloakTest {
     protected LoginPage loginPage;
 
     @Page
+    protected LoginPasswordResetPage resetPasswordPage;
+
+    @Page
     private RegisterPage registerPage;
 
     @Page
@@ -215,8 +225,6 @@ public class BruteForceTest extends AbstractTestRealmKeycloakTest {
         Assert.assertNull(response.getAccessToken());
     }
 
-
-
     @Test
     public void testGrantMissingOtp() throws Exception {
         {
@@ -384,6 +392,27 @@ public class BruteForceTest extends AbstractTestRealmKeycloakTest {
 
     }
 
+    @Test
+    public void testResetPassword() throws Exception {
+        String userId = adminClient.realm("test").users().search("test-user@localhost", null, null, null, 0, 1).get(0).getId();
+
+        loginSuccess();
+        loginInvalidPassword();
+        loginInvalidPassword();
+        expectTemporarilyDisabled();
+
+        loginPage.resetPassword();
+        resetPasswordPage.assertCurrent();
+        resetPasswordPage.changePassword("test-user@localhost");
+        loginPage.assertCurrent();
+
+        assertEquals("You should receive an email shortly with further instructions.", loginPage.getSuccessMessage());
+
+        events.expectRequiredAction(EventType.RESET_PASSWORD_ERROR).user(userId);
+        events.clear();
+        clearUserFailures();
+    }
+
     public void expectTemporarilyDisabled() throws Exception {
         expectTemporarilyDisabled("test-user@localhost", null);
     }
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/RestartCookieTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/RestartCookieTest.java
new file mode 100644
index 0000000..6135c56
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/RestartCookieTest.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright 2017 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.forms;
+
+import java.io.IOException;
+
+import javax.mail.MessagingException;
+
+import org.jboss.arquillian.container.test.api.Deployment;
+import org.jboss.arquillian.graphene.page.Page;
+import org.jboss.shrinkwrap.api.spec.WebArchive;
+import org.junit.Rule;
+import org.junit.Test;
+import org.keycloak.admin.client.resource.UserResource;
+import org.keycloak.events.Details;
+import org.keycloak.events.Errors;
+import org.keycloak.jose.jws.JWSBuilder;
+import org.keycloak.models.KeyManager;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.protocol.RestartLoginCookie;
+import org.keycloak.representations.idm.RealmRepresentation;
+import org.keycloak.testsuite.AbstractTestRealmKeycloakTest;
+import org.keycloak.testsuite.Assert;
+import org.keycloak.testsuite.AssertEvents;
+import org.keycloak.testsuite.pages.LoginPage;
+import org.keycloak.testsuite.runonserver.RunOnServerDeployment;
+import org.openqa.selenium.Cookie;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class RestartCookieTest extends AbstractTestRealmKeycloakTest {
+
+
+    @Page
+    protected LoginPage loginPage;
+
+
+    @Rule
+    public AssertEvents events = new AssertEvents(this);
+
+
+    @Deployment
+    public static WebArchive deploy() {
+        return RunOnServerDeployment.create(UserResource.class)
+                .addPackages(true, "org.keycloak.testsuite");
+    }
+
+
+    // KC_RESTART cookie from Keycloak 3.1.0
+    private static final String OLD_RESTART_COOKIE_JSON = "{\n" +
+            "  \"cs\": \"874a1ea8-5579-4f21-add0-903dd8e3ec1b\",\n" +
+            "  \"cid\": \"test-app\",\n" +
+            "  \"pty\": \"openid-connect\",\n" +
+            "  \"ruri\": \"http://localhost:8081/auth/realms/master/app/auth\",\n" +
+            "  \"act\": \"AUTHENTICATE\",\n" +
+            "  \"notes\": {\n" +
+            "    \"auth_type\": \"code\",\n" +
+            "    \"scope\": \"openid\",\n" +
+            "    \"iss\": \"http://localhost:8081/auth/realms/master/app/auth\",\n" +
+            "    \"response_type\": \"code\",\n" +
+            "    \"redirect_uri\": \"http://localhost:8081/auth/realms/master/app/auth/\",\n" +
+            "    \"state\": \"6c983e5b-2dc1-411a-9ed1-0f51095949c5\",\n" +
+            "    \"code_challenge_method\": \"plain\",\n" +
+            "    \"nonce\": \"65639660-99b2-4cdf-bc9f-9978fdce5b03\",\n" +
+            "    \"response_mode\": \"fragment\"\n" +
+            "  }\n" +
+            "}";
+
+    @Override
+    public void configureTestRealm(RealmRepresentation testRealm) {
+    }
+
+
+    // KEYCLOAK-5440
+    @Test
+    public void invalidLoginAndBackButton() throws IOException, MessagingException {
+        String oldRestartCookie = testingClient.server().fetchString((KeycloakSession session) -> {
+            try {
+                String cookieVal = OLD_RESTART_COOKIE_JSON.replace("\n", "").replace(" ", "");
+                RealmModel realm = session.realms().getRealmByName("test");
+
+                KeyManager.ActiveHmacKey activeKey = session.keys().getActiveHmacKey(realm);
+
+                String encodedToken = new JWSBuilder()
+                        .kid(activeKey.getKid())
+                        .content(cookieVal.getBytes("UTF-8"))
+                        .hmac256(activeKey.getSecretKey());
+
+                return encodedToken;
+
+
+            } catch (IOException ioe) {
+                throw new RuntimeException(ioe);
+            }
+        });
+
+        oauth.openLoginForm();
+
+        driver.manage().deleteAllCookies();
+        driver.manage().addCookie(new Cookie(RestartLoginCookie.KC_RESTART, oldRestartCookie));
+
+        loginPage.login("foo", "bar");
+        loginPage.assertCurrent();
+        Assert.assertEquals("You took too long to login. Login process starting from beginning.", loginPage.getError());
+
+        events.expectLogin().user((String) null).session((String) null).error(Errors.EXPIRED_CODE).clearDetails()
+                .detail(Details.RESTART_AFTER_TIMEOUT, "true")
+                .client((String) null)
+                .assertEvent();
+    }
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/OIDCProtocolMappersTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/OIDCProtocolMappersTest.java
index d281196..61d8945 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/OIDCProtocolMappersTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/OIDCProtocolMappersTest.java
@@ -52,7 +52,7 @@ import static org.hamcrest.Matchers.containsInAnyOrder;
 import static org.hamcrest.Matchers.hasItems;
 import static org.hamcrest.Matchers.instanceOf;
 import static org.hamcrest.Matchers.is;
-import static org.hamcrest.Matchers.isEmptyString;
+import static org.hamcrest.Matchers.isEmptyOrNullString;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
@@ -266,7 +266,7 @@ public class OIDCProtocolMappersTest extends AbstractKeycloakTest {
 
             IDToken idToken = oauth.verifyIDToken(response.getIdToken());
             Object empty = idToken.getOtherClaims().get("empty");
-            assertThat((empty == null ? null : (String) empty), isEmptyString());
+            assertThat((empty == null ? null : (String) empty), isEmptyOrNullString());
             Object nulll = idToken.getOtherClaims().get("null");
             assertNull(nulll);
 
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/URLAssert.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/URLAssert.java
index 2fd0cb6..e22f8c4 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/URLAssert.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/URLAssert.java
@@ -46,47 +46,85 @@ import static org.keycloak.testsuite.util.URLUtils.currentUrlStartWith;
  */
 public class URLAssert {
 
-    public static void assertCurrentUrlEquals(AbstractPage page) {
-        assertCurrentUrlEquals(page.getDriver(), page);
+    public static void assertCurrentUrlEquals(final AbstractPage page, WebDriver driver) {
+        assertCurrentUrlEquals(page.toString(), driver);
     }
 
-    public static void assertCurrentUrlEquals(WebDriver driver, final AbstractPage page) {
-        String expected = page.toString();
-        assertTrue("Expected URL: " + expected + "; actual: " + driver.getCurrentUrl(),
-                currentUrlEqual(page.toString()));
+    public static void assertCurrentUrlEquals(final String url, WebDriver driver) {
+        DroneUtils.addWebDriver(driver);
+        assertCurrentUrlEquals(url);
+        DroneUtils.removeWebDriver();
     }
 
-    public static void assertCurrentUrlEquals(WebDriver driver, final String url) {
-        assertTrue("Expected URL: " + url + "; actual: " + driver.getCurrentUrl(),
+    public static void assertCurrentUrlEquals(final AbstractPage page) {
+        assertCurrentUrlEquals(page.toString());
+    }
+
+    public static void assertCurrentUrlEquals(final String url) {
+        assertTrue("Expected URL: " + url + "; actual: " + DroneUtils.getCurrentDriver().getCurrentUrl(),
                 currentUrlEqual(url));
     }
 
-    public static void assertCurrentUrlStartsWith(AbstractPage page) {
-        assertCurrentUrlStartsWith(page.getDriver(), page.toString());
+
+    public static void assertCurrentUrlStartsWith(final AbstractPage page, WebDriver driver) {
+        assertCurrentUrlStartsWith(page.toString(), driver);
+    }
+
+    public static void assertCurrentUrlStartsWith(final String url, WebDriver driver) {
+        DroneUtils.addWebDriver(driver);
+        assertCurrentUrlStartsWith(url);
+        DroneUtils.removeWebDriver();
     }
 
-    public static void assertCurrentUrlStartsWith(WebDriver driver, final String url) {
-        assertTrue("URL expected to begin with:" + url + "; actual URL: " + driver.getCurrentUrl(),
-                currentUrlStartWith(url));
+   public static void assertCurrentUrlStartsWith(final AbstractPage page) {
+        assertCurrentUrlStartsWith(page.toString());
+   }
+
+    public static void assertCurrentUrlStartsWith(final String url){
+        assertTrue("URL expected to begin with:" + url + "; actual URL: " + DroneUtils.getCurrentDriver().getCurrentUrl(),
+        currentUrlStartWith(url));
+    }
+
+
+    public static void assertCurrentUrlDoesntStartWith(final AbstractPage page, WebDriver driver) {
+        assertCurrentUrlDoesntStartWith(page.toString(), driver);
+    }
+
+    public static void assertCurrentUrlDoesntStartWith(final String url, WebDriver driver) {
+        DroneUtils.addWebDriver(driver);
+        assertCurrentUrlDoesntStartWith(url);
+        DroneUtils.removeWebDriver();
     }
 
     public static void assertCurrentUrlDoesntStartWith(AbstractPage page) {
-        assertCurrentUrlDoesntStartWith(page.getDriver(), page.toString());
+        assertCurrentUrlDoesntStartWith(page.toString());
     }
 
-    public static void assertCurrentUrlDoesntStartWith(WebDriver driver, final String url) {
-        assertTrue("URL expected NOT to begin with:" + url + "; actual URL: " + driver.getCurrentUrl(),
+    public static void assertCurrentUrlDoesntStartWith(final String url) {
+        assertTrue("URL expected NOT to begin with:" + url + "; actual URL: " + DroneUtils.getCurrentDriver().getCurrentUrl(),
                 currentUrlDoesntStartWith(url));
     }
 
-    public static void assertCurrentUrlStartsWithLoginUrlOf(PageWithLoginUrl page) {
-        assertCurrentUrlStartsWithLoginUrlOf(page.getDriver(), page);
+
+    public static void assertCurrentUrlStartsWithLoginUrlOf(final PageWithLoginUrl page, WebDriver driver) {
+        assertCurrentUrlStartsWithLoginUrlOf(page.getOIDCLoginUrl().toString(), driver);
+    }
+
+    public static void assertCurrentUrlStartsWithLoginUrlOf(final String url, WebDriver driver) {
+        DroneUtils.addWebDriver(driver);
+        assertCurrentUrlStartsWithLoginUrlOf(url);
+        DroneUtils.removeWebDriver();
+    }
+
+    public static void assertCurrentUrlStartsWithLoginUrlOf(final PageWithLoginUrl page) {
+        assertCurrentUrlStartsWithLoginUrlOf(page.getOIDCLoginUrl().toString());
     }
 
-    public static void assertCurrentUrlStartsWithLoginUrlOf(WebDriver driver, PageWithLoginUrl page) {
-        assertCurrentUrlStartsWith(driver, page.getOIDCLoginUrl().toString());
+    public static void assertCurrentUrlStartsWithLoginUrlOf(final String url) {
+        assertCurrentUrlStartsWith(url);
     }
 
+
     public static void assertGetURL(URI url, String accessToken, AssertResponseHandler handler) {
         CloseableHttpClient httpclient = HttpClients.createDefault();
         try {
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/arquillian.xml b/testsuite/integration-arquillian/tests/base/src/test/resources/arquillian.xml
index 55fa5e2..1951857 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/resources/arquillian.xml
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/arquillian.xml
@@ -64,7 +64,7 @@
     
     <container qualifier="auth-server-undertow" mode="suite" >
         <configuration>
-            <property name="enabled">${auth.server.undertow} &amp;&amp; ! ${auth.server.undertow.crossdc}</property>
+            <property name="enabled">${auth.server.undertow} &amp;&amp; ! ${auth.server.crossdc}</property>
             <property name="bindAddress">0.0.0.0</property>
             <property name="adapterImplClass">org.keycloak.testsuite.arquillian.undertow.KeycloakOnUndertow</property>
             <property name="bindHttpPort">${auth.server.http.port}</property>
@@ -74,7 +74,7 @@
     
     <container qualifier="auth-server-${auth.server}" mode="suite" >
         <configuration>
-            <property name="enabled">${auth.server.jboss}</property>
+            <property name="enabled">${auth.server.jboss} &amp;&amp; ! ${auth.server.crossdc}</property>
             <property name="adapterImplClass">${auth.server.adapter.impl.class}</property>
             <property name="jbossHome">${auth.server.home}</property>
             <property name="${auth.server.config.property.name}">${auth.server.config.property.value}</property>
@@ -186,18 +186,18 @@
     </group>
 
 
-    <!-- Cross DC with embedded undertow. Node numbering is [centre #].[node #] -->
-    <group qualifier="auth-server-undertow-cross-dc">
+    <!-- Cross DC. Node numbering is [centre #].[node #] -->
+    <group qualifier="auth-server-jboss-cross-dc">
         <container qualifier="cache-server-cross-dc-1" mode="suite" >
             <configuration>
-                <property name="enabled">${auth.server.undertow.crossdc} &amp;&amp; ! ${cache.server.lifecycle.skip}</property>
+                <property name="enabled">${auth.server.crossdc} &amp;&amp; ! ${cache.server.lifecycle.skip}</property>
                 <property name="adapterImplClass">org.jboss.as.arquillian.container.managed.ManagedDeployableContainer</property>
                 <property name="jbossHome">${cache.server.home}</property>
                 <property name="serverConfig">clustered.xml</property>
                 <property name="jbossArguments">
                     -Djboss.socket.binding.port-offset=${cache.server.port.offset}
                     -Djboss.default.multicast.address=234.56.78.99
-                    -Djboss.node.name=cache-server
+                    -Djboss.node.name=cache-server-dc-1
                     ${adapter.test.props}
                     ${auth.server.profile}
                 </property>
@@ -213,7 +213,7 @@
 
         <container qualifier="cache-server-cross-dc-2" mode="suite" >
             <configuration>
-                <property name="enabled">${auth.server.undertow.crossdc} &amp;&amp; ! ${cache.server.lifecycle.skip}</property>
+                <property name="enabled">${auth.server.crossdc} &amp;&amp; ! ${cache.server.lifecycle.skip}</property>
                 <property name="adapterImplClass">org.jboss.as.arquillian.container.managed.ManagedDeployableContainer</property>
                 <property name="jbossHome">${cache.server.home}</property>
                 <property name="setupCleanServerBaseDir">true</property>
@@ -238,11 +238,11 @@
 
         <container qualifier="auth-server-balancer-cross-dc" mode="suite" >
             <configuration>
-                <property name="enabled">${auth.server.undertow.crossdc}</property>
+                <property name="enabled">${auth.server.crossdc}</property>
                 <property name="adapterImplClass">org.keycloak.testsuite.arquillian.undertow.lb.SimpleUndertowLoadBalancerContainer</property>
                 <property name="bindAddress">localhost</property>
                 <property name="bindHttpPort">${auth.server.http.port}</property>
-                <property name="nodes">auth-server-undertow-cross-dc-0_1=http://localhost:8101,auth-server-undertow-cross-dc-0_2-manual=http://localhost:8102,auth-server-undertow-cross-dc-1_1=http://localhost:8111,auth-server-undertow-cross-dc-1_2-manual=http://localhost:8112</property>
+                <property name="nodes">auth-server-${node.name}-cross-dc-0_1=http://localhost:8101,auth-server-${node.name}-cross-dc-0_2-manual=http://localhost:8102,auth-server-${node.name}-cross-dc-1_1=http://localhost:8111,auth-server-${node.name}-cross-dc-1_2-manual=http://localhost:8112</property>
             </configuration>
         </container>
 
@@ -253,7 +253,6 @@
                 <property name="bindAddress">localhost</property>
                 <property name="bindHttpPort">${auth.server.http.port}</property>
                 <property name="bindHttpPortOffset">-79</property>
-                <property name="route">auth-server-undertow-cross-dc-0_1</property>
                 <property name="remoteMode">${undertow.remote}</property>
                 <property name="dataCenter">0</property>
                 <property name="keycloakConfigPropertyOverrides">{
@@ -277,7 +276,6 @@
                 <property name="bindAddress">localhost</property>
                 <property name="bindHttpPort">${auth.server.http.port}</property>
                 <property name="bindHttpPortOffset">-78</property>
-                <property name="route">auth-server-undertow-cross-dc-0_2-manual</property>
                 <property name="remoteMode">${undertow.remote}</property>
                 <property name="dataCenter">0</property>
                 <property name="keycloakConfigPropertyOverrides">{
@@ -302,7 +300,6 @@
                 <property name="bindAddress">localhost</property>
                 <property name="bindHttpPort">${auth.server.http.port}</property>
                 <property name="bindHttpPortOffset">-69</property>
-                <property name="route">auth-server-undertow-cross-dc-1_1</property>
                 <property name="remoteMode">${undertow.remote}</property>
                 <property name="dataCenter">1</property>
                 <property name="keycloakConfigPropertyOverrides">{
@@ -326,7 +323,6 @@
                 <property name="bindAddress">localhost</property>
                 <property name="bindHttpPort">${auth.server.http.port}</property>
                 <property name="bindHttpPortOffset">-68</property>
-                <property name="route">auth-server-undertow-cross-dc-1_2-manual</property>
                 <property name="remoteMode">${undertow.remote}</property>
                 <property name="dataCenter">1</property>
                 <property name="keycloakConfigPropertyOverrides">{
@@ -343,8 +339,101 @@
                 }</property>
             </configuration>
         </container>
-    </group>
+        <container qualifier="auth-server-jboss-cross-dc-0_1" mode="suite" >
+            <configuration>
+                <property name="enabled">${auth.server.jboss.crossdc}</property>
+                <property name="adapterImplClass">${auth.server.adapter.impl.class}</property>
+                <property name="jbossHome">${auth.server.crossdc01.home}</property>
+                <property name="serverConfig">standalone-ha.xml</property>
+                <property name="jbossArguments">
+                    -Djboss.socket.binding.port-offset=${auth.server.crossdc01.port.offset}
+                    -Djboss.default.multicast.address=234.56.78.1
+                    -Dremote.cache.port=12232
+                    -Djboss.site.name=dc0
+                    -Djboss.node.name=auth-server-${node.name}-cross-dc-0_1
+                </property>
+                <property name="javaVmArguments">
+                    -Djava.net.preferIPv4Stack=true
+                    ${auth.server.crossdc01.jvm.debug.args}
+                </property>
+                <property name="managementPort">${auth.server.crossdc01.management.port}</property>
+                <property name="bindHttpPortOffset">-79</property>
+                <property name="dataCenter">0</property>
+                <property name="startupTimeoutInSeconds">${auth.server.jboss.startup.timeout}</property>
+            </configuration>
+        </container>
+        <container qualifier="auth-server-jboss-cross-dc-0_2-manual" mode="manual" >
+            <configuration>
+                <property name="enabled">${auth.server.jboss.crossdc}</property>
+                <property name="adapterImplClass">${auth.server.adapter.impl.class}</property>
+                <property name="jbossHome">${auth.server.crossdc02.home}</property>
+                <property name="serverConfig">standalone-ha.xml</property>
+                <property name="jbossArguments">
+                    -Djboss.socket.binding.port-offset=${auth.server.crossdc02.port.offset}
+                    -Djboss.default.multicast.address=234.56.78.1
+                    -Dremote.cache.port=12232
+                    -Djboss.site.name=dc0
+                    -Djboss.node.name=auth-server-${node.name}-cross-dc-0_2-manual
+                </property>
+                <property name="javaVmArguments">
+                    -Djava.net.preferIPv4Stack=true
+                    ${auth.server.crossdc02.jvm.debug.args}
+                </property>
+                <property name="managementPort">${auth.server.crossdc02.management.port}</property>
+                <property name="bindHttpPortOffset">-78</property>
+                <property name="dataCenter">0</property>
+                <property name="startupTimeoutInSeconds">${auth.server.jboss.startup.timeout}</property>
+            </configuration>
+        </container>
 
+        <container qualifier="auth-server-jboss-cross-dc-1_1" mode="suite" >
+            <configuration>
+                <property name="enabled">${auth.server.jboss.crossdc}</property>
+                <property name="adapterImplClass">${auth.server.adapter.impl.class}</property>
+                <property name="jbossHome">${auth.server.crossdc11.home}</property>
+                <property name="serverConfig">standalone-ha.xml</property>
+                <property name="jbossArguments">
+                    -Djboss.socket.binding.port-offset=${auth.server.crossdc11.port.offset}
+                    -Djboss.default.multicast.address=234.56.78.2
+                    -Dremote.cache.port=13232
+                    -Djboss.site.name=dc1
+                    -Djboss.node.name=auth-server-${node.name}-cross-dc-1_1
+                </property>
+                <property name="javaVmArguments">
+                    -Djava.net.preferIPv4Stack=true
+                    ${auth.server.crossdc11.jvm.debug.args}
+                </property>
+                <property name="managementPort">${auth.server.crossdc11.management.port}</property>
+                <property name="bindHttpPortOffset">-69</property>
+                <property name="dataCenter">1</property>
+                <property name="startupTimeoutInSeconds">${auth.server.jboss.startup.timeout}</property>
+            </configuration>
+        </container>
+        <container qualifier="auth-server-jboss-cross-dc-1_2-manual" mode="manual" >
+            <configuration>
+                <property name="enabled">${auth.server.jboss.crossdc}</property>
+                <property name="adapterImplClass">${auth.server.adapter.impl.class}</property>
+                <property name="jbossHome">${auth.server.crossdc12.home}</property>
+                <property name="serverConfig">standalone-ha.xml</property>
+                <property name="jbossArguments">
+                    -Djboss.socket.binding.port-offset=${auth.server.crossdc12.port.offset}
+                    -Djboss.default.multicast.address=234.56.78.2
+                    -Dremote.cache.port=13232
+                    -Djboss.site.name=dc1
+                    -Djboss.node.name=auth-server-${node.name}-cross-dc-1_2-manual
+                </property>
+                <property name="javaVmArguments">
+                    -Djava.net.preferIPv4Stack=true
+                    ${auth.server.crossdc12.jvm.debug.args}
+                </property>
+                <property name="managementPort">${auth.server.crossdc12.management.port}</property>
+                <property name="bindHttpPortOffset">-68</property>
+                <property name="dataCenter">1</property>
+                <property name="startupTimeoutInSeconds">${auth.server.jboss.startup.timeout}</property>
+            </configuration>
+        </container>
+    </group>
+    
 
     <container qualifier="auth-server-balancer-wildfly" mode="suite" >
         <configuration>
diff --git a/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly/src/test/java/org/keycloak/testsuite/adapter/example/hal/WildflyConsoleProtectionTest.java b/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly/src/test/java/org/keycloak/testsuite/adapter/example/hal/WildflyConsoleProtectionTest.java
index 8057da7..ce2ab8d 100644
--- a/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly/src/test/java/org/keycloak/testsuite/adapter/example/hal/WildflyConsoleProtectionTest.java
+++ b/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly/src/test/java/org/keycloak/testsuite/adapter/example/hal/WildflyConsoleProtectionTest.java
@@ -19,20 +19,25 @@ package org.keycloak.testsuite.adapter.example.hal;
 import static org.junit.Assert.assertTrue;
 import static org.keycloak.testsuite.util.IOUtil.loadRealm;
 
+import java.io.IOException;
 import java.util.List;
 
 import org.jboss.arquillian.graphene.page.Page;
+import org.junit.Assume;
 import org.junit.Before;
 import org.junit.Test;
 import org.keycloak.representations.idm.RealmRepresentation;
 import org.keycloak.testsuite.adapter.AbstractAdapterTest;
+import org.keycloak.testsuite.arquillian.AppServerTestEnricher;
 import org.keycloak.testsuite.arquillian.annotation.AppServerContainer;
 import org.keycloak.testsuite.pages.AccountUpdateProfilePage;
 import org.keycloak.testsuite.pages.AppServerWelcomePage;
 import org.keycloak.testsuite.util.WaitUtils;
-import org.wildfly.extras.creaper.core.ManagementClient;
+import org.wildfly.extras.creaper.core.online.CliException;
 import org.wildfly.extras.creaper.core.online.OnlineManagementClient;
-import org.wildfly.extras.creaper.core.online.OnlineOptions;
+import org.wildfly.extras.creaper.core.online.operations.Address;
+import org.wildfly.extras.creaper.core.online.operations.OperationException;
+import org.wildfly.extras.creaper.core.online.operations.Operations;
 
 /**
  *
@@ -54,14 +59,17 @@ public class WildflyConsoleProtectionTest extends AbstractAdapterTest {
     }
 
     @Before
-    public void beforeAuthTest() {
+    public void beforeAuthTest() throws IOException, OperationException {
         super.beforeAuthTest();
 
+        OnlineManagementClient clientWorkerNodeClient = null;
+
         try {
-            OnlineManagementClient clientWorkerNodeClient = ManagementClient.online(OnlineOptions
-                    .standalone()
-                    .hostAndPort("localhost", 10190)
-                    .build());
+            clientWorkerNodeClient = AppServerTestEnricher.getManagementClient();
+
+            Operations operations = new Operations(clientWorkerNodeClient);
+
+            Assume.assumeTrue(operations.exists(Address.subsystem("elytron")));
 
             // Create a realm for both wildfly console and mgmt interface
             clientWorkerNodeClient.execute("/subsystem=keycloak/realm=jboss-infra:add(auth-server-url=http://localhost:8180/auth,realm-public-key=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB)");
@@ -84,8 +92,12 @@ public class WildflyConsoleProtectionTest extends AbstractAdapterTest {
 
             // reload
             clientWorkerNodeClient.execute("reload");
-        } catch (Exception cause) {
+        } catch (CliException cause) {
             throw new RuntimeException("Failed to configure app server", cause);
+        } finally {
+            if (clientWorkerNodeClient != null) {
+                clientWorkerNodeClient.close();
+            }
         }
     }
 
diff --git a/testsuite/integration-arquillian/tests/pom.xml b/testsuite/integration-arquillian/tests/pom.xml
index 43de937..3ea5ad3 100755
--- a/testsuite/integration-arquillian/tests/pom.xml
+++ b/testsuite/integration-arquillian/tests/pom.xml
@@ -41,10 +41,12 @@
     <properties>
         <auth.server>undertow</auth.server>
         <auth.server.undertow>true</auth.server.undertow>
-        <auth.server.undertow.crossdc>false</auth.server.undertow.crossdc>
+
         <auth.server.crossdc>false</auth.server.crossdc>
+        <auth.server.undertow.crossdc>false</auth.server.undertow.crossdc>
+        <auth.server.jboss.crossdc>false</auth.server.jboss.crossdc>
         <cache.server.lifecycle.skip>false</cache.server.lifecycle.skip>
-        
+
         <auth.server.container>auth-server-${auth.server}</auth.server.container>
         <auth.server.home>${containers.home}/${auth.server.container}</auth.server.home>
         <auth.server.config.dir>${auth.server.home}</auth.server.config.dir>
@@ -60,16 +62,16 @@
         <auth.server.memory.settings>-Xms64m -Xmx512m -XX:MetaspaceSize=96M -XX:MaxMetaspaceSize=256m</auth.server.memory.settings>
         <auth.server.config.property.name>serverConfig</auth.server.config.property.name>
         <auth.server.adapter.impl.class>org.jboss.as.arquillian.container.managed.ManagedDeployableContainer</auth.server.adapter.impl.class>
-        
+
         <auth.server.jboss.artifactId>integration-arquillian-servers-auth-server-${auth.server}</auth.server.jboss.artifactId>
         <auth.server.jboss.skip.unpack>${auth.server.undertow}</auth.server.jboss.skip.unpack>
         <auth.server.jboss.startup.timeout>300</auth.server.jboss.startup.timeout>
-        
+
         <!--debug properties-->
         <auth.server.debug.port>5005</auth.server.debug.port>
         <auth.server.debug.suspend>n</auth.server.debug.suspend>
         <auth.server.jboss.jvm.debug.args>-agentlib:jdwp=transport=dt_socket,server=y,suspend=${auth.server.debug.suspend},address=${auth.server.host}:${auth.server.debug.port}</auth.server.jboss.jvm.debug.args>
-        
+
         <auth.server.remote>false</auth.server.remote>
         <auth.server.profile/>
         <auth.server.feature/>
@@ -91,7 +93,7 @@
         <adapter.test.props/>
         <migration.import.properties/>
         <kie.maven.settings/>
-        
+
         <examples.home>${project.build.directory}/examples</examples.home>
 
         <browser>htmlUnit</browser>
@@ -242,10 +244,10 @@
                             <auth.server.config.property.value>${auth.server.config.property.value}</auth.server.config.property.value>
                             <auth.server.adapter.impl.class>${auth.server.adapter.impl.class}</auth.server.adapter.impl.class>
                             <auth.server.jboss.jvm.debug.args>${auth.server.jboss.jvm.debug.args}</auth.server.jboss.jvm.debug.args>
-                            
+
                             <auth.server.profile>${auth.server.profile}</auth.server.profile>
                             <auth.server.feature>${auth.server.feature}</auth.server.feature>
-                            
+
                             <frontend.console.output>${frontend.console.output}</frontend.console.output>
                             <backends.console.output>${backend.console.output}</backends.console.output>
 
@@ -254,7 +256,7 @@
                             <adapter.test.props>${adapter.test.props}</adapter.test.props>
                             <migration.import.properties>${migration.import.properties}</migration.import.properties>
                             <kie.maven.settings>${kie.maven.settings}</kie.maven.settings>
-                            
+
                             <testsuite.constants>${testsuite.constants}</testsuite.constants>
                             <cli.log.output>${cli.log.output}</cli.log.output>
                             <test.intermittent>${test.intermittent}</test.intermittent>
@@ -287,10 +289,11 @@
                             <client.key.passphrase>${client.key.passphrase}</client.key.passphrase>
 
                             <auth.server.ocsp.responder.enabled>${auth.server.ocsp.responder.enabled}</auth.server.ocsp.responder.enabled>
-                            
+
                             <!--cache server properties-->
                             <auth.server.crossdc>${auth.server.crossdc}</auth.server.crossdc>
                             <auth.server.undertow.crossdc>${auth.server.undertow.crossdc}</auth.server.undertow.crossdc>
+                            <auth.server.jboss.crossdc>${auth.server.jboss.crossdc}</auth.server.jboss.crossdc>
                             <cache.server.lifecycle.skip>${cache.server.lifecycle.skip}</cache.server.lifecycle.skip>
 
                             <cache.server>${cache.server}</cache.server>
@@ -367,14 +370,13 @@
                 </dependency>
             </dependencies>
         </profile>
-            
+
         <profile>
             <id>auth-server-wildfly</id>
             <properties>
                 <auth.server>wildfly</auth.server>
                 <auth.server.jboss>true</auth.server.jboss>
                 <auth.server.undertow>false</auth.server.undertow>
-                <auth.server.undertow.crossdc>false</auth.server.undertow.crossdc>
                 <auth.server.config.property.value>standalone.xml</auth.server.config.property.value>
                 <auth.server.config.dir>${auth.server.home}/standalone/configuration</auth.server.config.dir>
                 <h2.version>1.3.173</h2.version>
@@ -393,7 +395,6 @@
                 <auth.server>eap</auth.server>
                 <auth.server.jboss>true</auth.server.jboss>
                 <auth.server.undertow>false</auth.server.undertow>
-                <auth.server.undertow.crossdc>false</auth.server.undertow.crossdc>
                 <auth.server.config.property.value>standalone.xml</auth.server.config.property.value>
                 <auth.server.config.dir>${auth.server.home}/standalone/configuration</auth.server.config.dir>
                 <h2.version>1.3.173</h2.version>
@@ -407,13 +408,198 @@
         </profile>
 
         <profile>
+            <id>auth-servers-crossdc-undertow</id>
+            <properties>
+                <auth.servers.crossdc>true</auth.servers.crossdc>
+                <auth.server.undertow.crossdc>true</auth.server.undertow.crossdc>
+                <node.name>undertow</node.name>
+            </properties>
+            <build>
+                <plugins>
+                    <plugin>
+                        <artifactId>maven-enforcer-plugin</artifactId>
+                        <executions>
+                            <execution>
+                                <id>enforce-profile-activation</id>
+                                <goals>
+                                    <goal>enforce</goal>
+                                </goals>
+                                <configuration>
+                                    <rules>
+                                        <requireProperty>
+                                            <property>cache.server.jboss</property>
+                                            <message>Profile "auth-servers-crossdc-undertow" requires activation of another profile: either "cache-server-infinispan" or "cache-server-jdg".</message>
+                                            <regex>true</regex>
+                                        </requireProperty>
+                                    </rules>
+                                </configuration>
+                            </execution>
+                        </executions>
+                    </plugin>
+                    <plugin>
+                        <artifactId>maven-surefire-plugin</artifactId>
+                        <configuration>
+                            <systemPropertyVariables>
+                                <node.name>${node.name}</node.name>
+                                <pageload.timeout>20000</pageload.timeout>
+                            </systemPropertyVariables>
+                        </configuration>
+                    </plugin>
+                </plugins>
+            </build>
+        </profile>
+
+        <profile>
+            <id>auth-servers-crossdc-jboss</id>
+            <properties>
+                <auth.servers.crossdc>true</auth.servers.crossdc>
+                <auth.server.jboss.crossdc>true</auth.server.jboss.crossdc>
+                <node.name>jboss</node.name>
+
+                <auth.server.crossdc01.home>${containers.home}/auth-server-${auth.server}-crossdc01</auth.server.crossdc01.home>
+                <auth.server.crossdc02.home>${containers.home}/auth-server-${auth.server}-crossdc02</auth.server.crossdc02.home>
+                <auth.server.crossdc11.home>${containers.home}/auth-server-${auth.server}-crossdc11</auth.server.crossdc11.home>
+                <auth.server.crossdc12.home>${containers.home}/auth-server-${auth.server}-crossdc12</auth.server.crossdc12.home>
+
+                <!-- property specifies keycloak-add-user.json file destination -->
+                <auth.server.config.dir>${auth.server.crossdc01.home}/standalone/configuration</auth.server.config.dir>
+
+                <auth.server.crossdc01.jvm.debug.port>5001</auth.server.crossdc01.jvm.debug.port>
+                <auth.server.crossdc02.jvm.debug.port>5002</auth.server.crossdc02.jvm.debug.port>
+                <auth.server.crossdc11.jvm.debug.port>5011</auth.server.crossdc11.jvm.debug.port>
+                <auth.server.crossdc12.jvm.debug.port>5012</auth.server.crossdc12.jvm.debug.port>
+
+                <!-- default is "n", possible to override by e.g. -Dauth.server.crossdc01.debug.suspend=y -->
+                <auth.server.crossdc01.debug.suspend>${auth.server.debug.suspend}</auth.server.crossdc01.debug.suspend>
+                <auth.server.crossdc02.debug.suspend>${auth.server.debug.suspend}</auth.server.crossdc02.debug.suspend>
+                <auth.server.crossdc11.debug.suspend>${auth.server.debug.suspend}</auth.server.crossdc11.debug.suspend>
+                <auth.server.crossdc12.debug.suspend>${auth.server.debug.suspend}</auth.server.crossdc12.debug.suspend>
+            </properties>
+            <build>
+                <pluginManagement>
+                    <plugins>
+                        <plugin>
+                            <artifactId>maven-antrun-plugin</artifactId>
+                            <executions>
+                                <execution>
+                                    <id>copy-auth-server-crossdc-nodes</id>
+                                    <phase>process-resources</phase>
+                                    <goals>
+                                        <goal>run</goal>
+                                    </goals>
+                                    <configuration>
+                                        <target>
+                                            <move todir="${auth.server.crossdc01.home}">
+                                                <fileset dir="${auth.server.home}"/>
+                                            </move>
+                                            <copy todir="${auth.server.crossdc02.home}">
+                                                <fileset dir="${auth.server.crossdc01.home}"/>
+                                            </copy>
+                                            <copy todir="${auth.server.crossdc11.home}">
+                                                <fileset dir="${auth.server.crossdc01.home}"/>
+                                            </copy>
+                                            <copy todir="${auth.server.crossdc12.home}">
+                                                <fileset dir="${auth.server.crossdc01.home}"/>
+                                            </copy>
+                                        </target>
+                                    </configuration>
+                                </execution>
+                            </executions>
+                        </plugin>
+                    </plugins>
+                </pluginManagement>
+                <plugins>
+                    <plugin>
+                        <artifactId>maven-enforcer-plugin</artifactId>
+                        <executions>
+                            <execution>
+                                <id>enforce-profiles-activation</id>
+                                <goals>
+                                    <goal>enforce</goal>
+                                </goals>
+                                <configuration>
+                                    <rules>
+                                        <requireProperty>
+                                            <property>cache.server.jboss</property>
+                                            <message>Profile "auth-servers-crossdc-jboss" requires activation of another profile: either "cache-server-infinispan" or "cache-server-jdg".</message>
+                                            <regex>true</regex>
+                                        </requireProperty>
+                                        <requireProperty>
+                                            <property>auth.server.jboss</property>
+                                            <message>Profile "auth-servers-crossdc-jboss" requires activation of another profile: either "auth-server-wildfly" or "auth-server-eap".</message>
+                                            <regex>true</regex>
+                                        </requireProperty>
+                                    </rules>
+                                </configuration>
+                            </execution>
+                        </executions>
+                    </plugin>
+                    <plugin>
+                        <artifactId>maven-surefire-plugin</artifactId>
+                        <configuration>
+                            <systemPropertyVariables>
+
+                                <pageload.timeout>20000</pageload.timeout>
+
+                                <run.h2>true</run.h2>
+                                <node.name>${node.name}</node.name>
+
+                                <auth.server.crossdc01.home>${auth.server.crossdc01.home}</auth.server.crossdc01.home>
+                                <auth.server.crossdc02.home>${auth.server.crossdc02.home}</auth.server.crossdc02.home>
+                                <auth.server.crossdc11.home>${auth.server.crossdc11.home}</auth.server.crossdc11.home>
+                                <auth.server.crossdc12.home>${auth.server.crossdc12.home}</auth.server.crossdc12.home>
+
+                                <!--8101-->
+                                <auth.server.crossdc01.port.offset>21</auth.server.crossdc01.port.offset>
+                                <!--8102-->
+                                <auth.server.crossdc02.port.offset>22</auth.server.crossdc02.port.offset>
+                                <!--8111-->
+                                <auth.server.crossdc11.port.offset>31</auth.server.crossdc11.port.offset>
+                                <!--8112-->
+                                <auth.server.crossdc12.port.offset>32</auth.server.crossdc12.port.offset>
+
+                                <auth.server.crossdc01.management.port>10011</auth.server.crossdc01.management.port>
+                                <auth.server.crossdc02.management.port>10012</auth.server.crossdc02.management.port>
+                                <auth.server.crossdc11.management.port>10021</auth.server.crossdc11.management.port>
+                                <auth.server.crossdc12.management.port>10022</auth.server.crossdc12.management.port>
+
+                                <auth.server.crossdc01.jvm.debug.args>
+                                    -agentlib:jdwp=transport=dt_socket,server=y,suspend=${auth.server.crossdc01.debug.suspend},address=localhost:${auth.server.crossdc01.jvm.debug.port}
+                                </auth.server.crossdc01.jvm.debug.args>
+                                <auth.server.crossdc02.jvm.debug.args>
+                                    -agentlib:jdwp=transport=dt_socket,server=y,suspend=${auth.server.crossdc02.debug.suspend},address=localhost:${auth.server.crossdc02.jvm.debug.port}
+                                </auth.server.crossdc02.jvm.debug.args>
+                                <auth.server.crossdc11.jvm.debug.args>
+                                    -agentlib:jdwp=transport=dt_socket,server=y,suspend=${auth.server.crossdc11.debug.suspend},address=localhost:${auth.server.crossdc11.jvm.debug.port}
+                                </auth.server.crossdc11.jvm.debug.args>
+                                <auth.server.crossdc12.jvm.debug.args>
+                                    -agentlib:jdwp=transport=dt_socket,server=y,suspend=${auth.server.crossdc12.debug.suspend},address=localhost:${auth.server.crossdc12.jvm.debug.port}
+                                </auth.server.crossdc12.jvm.debug.args>
+
+                            </systemPropertyVariables>
+                        </configuration>
+                    </plugin>
+                    <plugin>
+                        <artifactId>maven-antrun-plugin</artifactId>
+                        <configuration>
+                            <!--
+                                skipping execution for <artifactId>integration-arquillian-tests</artifactId>,
+                                it's re-enabled in <artifactId>integration-arquillian-tests-base</artifactId>
+                            -->
+                            <skip>true</skip>
+                        </configuration>
+                    </plugin>
+                </plugins>
+            </build>
+        </profile>
+
+        <profile>
             <id>cache-server-infinispan</id>
             <properties>
                 <cache.server>infinispan</cache.server>
-                <auth.server.undertow.crossdc>true</auth.server.undertow.crossdc>
                 <auth.server.crossdc>true</auth.server.crossdc>
                 <cache.server.jboss>true</cache.server.jboss>
-                <cache.server.config.dir>${cache.server.home}/standalone/configuration</cache.server.config.dir>                
+                <cache.server.config.dir>${cache.server.home}/standalone/configuration</cache.server.config.dir>
                 <keycloak.testsuite.logging.pattern>%d{HH:mm:ss,SSS} [%t] %-5p [%c{1.}] %m%n</keycloak.testsuite.logging.pattern>
             </properties>
             <dependencies>
@@ -429,17 +615,15 @@
                         <artifactId>maven-enforcer-plugin</artifactId>
                         <executions>
                             <execution>
+                                <id>enforce-profile-activation</id>
                                 <goals>
                                     <goal>enforce</goal>
                                 </goals>
                                 <configuration>
                                     <rules>
-                                        <!--requireActiveProfile 'auth-server-wildfly/eap' doesn't work unless the profiles are defined in all submodule poms
-                                        using requireProperty instead-->
                                         <requireProperty>
-                                            <property>cache.server</property>
-                                            <regex>(infinispan)|(jdg)</regex>
-                                            <regexMessage>Profile "cache-server-infinispan" requires activation of profile "cache-server-infinispan" or "cache-server-jdg".</regexMessage>
+                                            <property>auth.servers.crossdc</property>
+                                            <message>Profile "cache-server-infinispan" requires activation of another profile: either "auth-servers-crossdc-undertow" or "auth-servers-crossdc-jboss".</message>
                                         </requireProperty>
                                     </rules>
                                 </configuration>
@@ -480,12 +664,11 @@
                 </pluginManagement>
             </build>
         </profile>
-        
+
         <profile>
             <id>cache-server-jdg</id>
             <properties>
                 <cache.server>jdg</cache.server>
-                <auth.server.undertow.crossdc>true</auth.server.undertow.crossdc>
                 <auth.server.crossdc>true</auth.server.crossdc>
                 <cache.server.jboss>true</cache.server.jboss>
                 <cache.server.config.dir>${cache.server.home}/standalone/configuration</cache.server.config.dir>
@@ -504,17 +687,15 @@
                         <artifactId>maven-enforcer-plugin</artifactId>
                         <executions>
                             <execution>
+                                <id>enforce-profile-activation</id>
                                 <goals>
                                     <goal>enforce</goal>
                                 </goals>
                                 <configuration>
                                     <rules>
-                                        <!--requireActiveProfile 'auth-server-wildfly/eap' doesn't work unless the profiles are defined in all submodule poms
-                                        using requireProperty instead-->
                                         <requireProperty>
-                                            <property>cache.server</property>
-                                            <regex>(infinispan)|(jdg)</regex>
-                                            <regexMessage>Profile "cache-server-jdg" requires activation of profile "cache-server-infinispan" or "cache-server-jdg".</regexMessage>
+                                            <property>auth.servers.crossdc</property>
+                                            <message>Profile "cache-server-jdg" requires activation of another profile: either "auth-servers-crossdc-undertow" or "auth-servers-crossdc-jboss".</message>
                                         </requireProperty>
                                     </rules>
                                 </configuration>
@@ -569,8 +750,8 @@
         </profile>
 
         <!--
-            profile that enables/disables specified feature, for more details see 
-            https://keycloak.gitbooks.io/documentation/content/server_installation/topics/profiles.html 
+            profile that enables/disables specified feature, for more details see
+            https://keycloak.gitbooks.io/documentation/content/server_installation/topics/profiles.html
         -->
         <profile>
             <id>auth-server-enable-disable-feature</id>
@@ -765,7 +946,7 @@
                 </pluginManagement>
             </build>
         </profile>
-        
+
         <profile>
             <id>jdbc-driver-dependency</id>
             <activation>
@@ -807,13 +988,13 @@
         </profile>
 
         <!-- Profiles for migration tests-->
-        
+
         <profile>
             <id>auth-server-migration</id>
             <properties>
                 <migration.import.file>target/test-classes/migration-test/migration-realm-${migrated.auth.server.version}.json</migration.import.file>
                 <migration.import.props.previous>
-                    -Dkeycloak.migration.action=import 
+                    -Dkeycloak.migration.action=import
                     -Dkeycloak.migration.provider=singleFile
                     -Dkeycloak.migration.file=${migration.import.file}
                     -Dkeycloak.migration.strategy=OVERWRITE_EXISTING
@@ -883,8 +1064,8 @@
                     </plugins>
                 </pluginManagement>
             </build>
-        </profile>    
-        
+        </profile>
+
         <profile>
             <id>migration-import</id>
             <activation>
@@ -896,7 +1077,7 @@
             <properties>
                 <migration.import.file>target/test-classes/migration-test/migration-realm-${migrated.auth.server.version}.json</migration.import.file>
                 <migration.import.properties>
-                    -Dkeycloak.migration.action=import 
+                    -Dkeycloak.migration.action=import
                     -Dkeycloak.migration.provider=singleFile
                     -Dkeycloak.migration.file=${migration.import.file}
                     -Dkeycloak.migration.strategy=OVERWRITE_EXISTING
@@ -934,7 +1115,7 @@
                 </plugins>
             </build>
         </profile>    
-        
+
         <profile>
             <id>migration-productized</id>
             <activation>
@@ -946,7 +1127,7 @@
                 <migration.import.file>target/test-classes/migration-test/migration-realm-${migrated.version.import.file.suffix}.json</migration.import.file>
             </properties>
         </profile>
-      
+
         <profile>
             <id>no-account</id>
             <properties>
@@ -1280,7 +1461,7 @@
                 <auth.server.management.port.jmx>9999</auth.server.management.port.jmx>
             </properties>
         </profile>
-        
+
         <profile>
             <!--see KEYCLOAK-4793-->
             <id>kie.maven.settings</id>
@@ -1299,7 +1480,7 @@
                 </kie.maven.settings>
             </properties>
         </profile>
-        
+
     </profiles>
-    
+
 </project>
diff --git a/testsuite/performance/docker-compose.yml b/testsuite/performance/docker-compose.yml
index 0ab4ae0..503f0d4 100644
--- a/testsuite/performance/docker-compose.yml
+++ b/testsuite/performance/docker-compose.yml
@@ -5,13 +5,14 @@ networks:
         ipam:
             config:
             - subnet: 10.0.1.0/24
-            
+
 services:
-    
+
     mariadb:
         build: db/mariadb
         image: keycloak_test_mariadb:${KEYCLOAK_VERSION:-latest}
-        cpus: 1
+        cpuset: ${DB_CPUSET:-1}
+        mem_limit: ${DB_MEMLIMIT:-1g}
         networks:
             - keycloak
         environment:
@@ -22,14 +23,15 @@ services:
             MYSQL_INITDB_SKIP_TZINFO: 1
         ports:
             - "3306:3306"
-            
+
     keycloak:
         build: keycloak
         image: keycloak_test_keycloak:${KEYCLOAK_VERSION:-latest}
         depends_on:
             mariadb:
                 condition: service_healthy
-        cpus: 1
+        cpuset: ${KEYCLOAK_CPUSET:-2-3}
+        mem_limit: ${KEYCLOAK_MEMLIMIT:-2500m}
         networks:
             - keycloak
         environment:
@@ -40,7 +42,7 @@ services:
             KEYCLOAK_USER: admin
             KEYCLOAK_PASSWORD: admin
             # docker-compose syntax note: ${ENV_VAR:-<DEFAULT_VALUE>}
-            JAVA_OPTS: ${KEYCLOAK_JVM_MEMORY:--Xms64m -Xmx512m -XX:MetaspaceSize=96M -XX:MaxMetaspaceSize=256m} -Djava.net.preferIPv4Stack=true -Djboss.modules.system.pkgs=org.jboss.byteman -Djava.awt.headless=true
+            JAVA_OPTS: ${KEYCLOAK_JVM_MEMORY:--Xms64m -Xmx2g -XX:MetaspaceSize=96M -XX:MaxMetaspaceSize=256m} -Djava.net.preferIPv4Stack=true -Djboss.modules.system.pkgs=org.jboss.byteman -Djava.awt.headless=true
             HTTP_MAX_CONNECTIONS: ${KEYCLOAK_HTTP_MAX_CONNECTIONS:-500}
             WORKER_IO_THREADS: ${KEYCLOAK_WORKER_IO_THREADS:-2}
             WORKER_TASK_MAX_THREADS: ${KEYCLOAK_WORKER_TASK_MAX_THREADS:-16}
@@ -50,4 +52,4 @@ services:
             DS_PS_CACHE_SIZE: ${KEYCLOAK_DS_PS_CACHE_SIZE:-100}
         ports:
             - "8080:8080"
-            - "9990:9990"
\ No newline at end of file
+            - "9990:9990"
diff --git a/testsuite/performance/docker-compose-cluster.yml b/testsuite/performance/docker-compose-cluster.yml
index d04bddd..4ecd95a 100644
--- a/testsuite/performance/docker-compose-cluster.yml
+++ b/testsuite/performance/docker-compose-cluster.yml
@@ -15,7 +15,8 @@ services:
     mariadb:
         build: db/mariadb
         image: keycloak_test_mariadb:${KEYCLOAK_VERSION:-latest}
-        cpus: 1
+        cpuset: ${DB_CPUSET:-1}
+        mem_limit: ${DB_MEMLIMIT:-1g}
         networks:
             - keycloak
         environment:
@@ -32,7 +33,8 @@ services:
         depends_on:
             mariadb:
                 condition: service_healthy
-        cpus: 1
+        cpuset: ${KEYCLOAK_CPUSET:-2-3}
+        mem_limit: ${KEYCLOAK_MEMLIMIT:-2500m}
         networks:
             - keycloak
         environment:
@@ -46,7 +48,7 @@ services:
             KEYCLOAK_USER: admin
             KEYCLOAK_PASSWORD: admin
 
-            JAVA_OPTS: ${KEYCLOAK_JVM_MEMORY:--Xms64m -Xmx512m -XX:MetaspaceSize=96M -XX:MaxMetaspaceSize=256m} -Djava.net.preferIPv4Stack=true -Djboss.modules.system.pkgs=org.jboss.byteman -Djava.awt.headless=true
+            JAVA_OPTS: ${KEYCLOAK_JVM_MEMORY:--Xms64m -Xmx2g -XX:MetaspaceSize=96M -XX:MaxMetaspaceSize=256m} -Djava.net.preferIPv4Stack=true -Djboss.modules.system.pkgs=org.jboss.byteman -Djava.awt.headless=true
             HTTP_MAX_CONNECTIONS: ${KEYCLOAK_HTTP_MAX_CONNECTIONS:-500}
             AJP_MAX_CONNECTIONS: ${KEYCLOAK_AJP_MAX_CONNECTIONS:-500}
             WORKER_IO_THREADS: ${KEYCLOAK_WORKER_IO_THREADS:-2}
diff --git a/testsuite/performance/docker-compose-crossdc.yml b/testsuite/performance/docker-compose-crossdc.yml
index e95f71d..5075ad9 100644
--- a/testsuite/performance/docker-compose-crossdc.yml
+++ b/testsuite/performance/docker-compose-crossdc.yml
@@ -95,7 +95,8 @@ services:
         depends_on: 
             mariadb_dc1:
                 condition: service_healthy
-        cpus: 1
+        cpuset: ${DB_CPUSET:-1}
+        mem_limit: ${DB_MEMLIMIT:-1g}
         networks:
             - db_replication
             - dc2_keycloak
@@ -122,7 +123,8 @@ services:
             # wait for the ispn cluster to be ready before starting keycloak
             infinispan_dc2:
                 condition: service_healthy
-        cpus: 1
+        cpuset: ${KEYCLOAK_DC1_CPUSET:-2}
+        mem_limit: ${KEYCLOAK_MEMLIMIT:-2500m}
         networks:
             - dc1_keycloak
         environment:
@@ -138,7 +140,7 @@ services:
             INFINISPAN_HOST: infinispan_dc1
             SITE: dc1
             
-            JAVA_OPTS: ${KEYCLOAK_JVM_MEMORY:--Xms64m -Xmx512m -XX:MetaspaceSize=96M -XX:MaxMetaspaceSize=256m} -Djava.net.preferIPv4Stack=true -Djboss.modules.system.pkgs=org.jboss.byteman -Djava.awt.headless=true
+            JAVA_OPTS: ${KEYCLOAK_JVM_MEMORY:--Xms64m -Xmx2g -XX:MetaspaceSize=96M -XX:MaxMetaspaceSize=256m} -Djava.net.preferIPv4Stack=true -Djboss.modules.system.pkgs=org.jboss.byteman -Djava.awt.headless=true
             HTTP_MAX_CONNECTIONS: ${KEYCLOAK_HTTP_MAX_CONNECTIONS:-500}
             AJP_MAX_CONNECTIONS: ${KEYCLOAK_AJP_MAX_CONNECTIONS:-500}
             WORKER_IO_THREADS: ${KEYCLOAK_WORKER_IO_THREADS:-2}
@@ -162,7 +164,8 @@ services:
             # wait for first kc instance to be ready before starting another
             keycloak_dc1:
                 condition: service_healthy
-        cpus: 1
+        cpuset: ${KEYCLOAK_DC2_CPUSET:-3}
+        mem_limit: ${KEYCLOAK_MEMLIMIT:-2500m}
         networks:
             - dc2_keycloak
         environment:
@@ -176,7 +179,7 @@ services:
             INFINISPAN_HOST: infinispan_dc2
             SITE: dc2
 
-            JAVA_OPTS: ${KEYCLOAK_JVM_MEMORY:--Xms64m -Xmx512m -XX:MetaspaceSize=96M -XX:MaxMetaspaceSize=256m} -Djava.net.preferIPv4Stack=true -Djboss.modules.system.pkgs=org.jboss.byteman -Djava.awt.headless=true
+            JAVA_OPTS: ${KEYCLOAK_JVM_MEMORY:--Xms64m -Xmx2g -XX:MetaspaceSize=96M -XX:MaxMetaspaceSize=256m} -Djava.net.preferIPv4Stack=true -Djboss.modules.system.pkgs=org.jboss.byteman -Djava.awt.headless=true
             HTTP_MAX_CONNECTIONS: ${KEYCLOAK_HTTP_MAX_CONNECTIONS:-500}
             AJP_MAX_CONNECTIONS: ${KEYCLOAK_AJP_MAX_CONNECTIONS:-500}
             WORKER_IO_THREADS: ${KEYCLOAK_WORKER_IO_THREADS:-2}
diff --git a/testsuite/performance/docker-compose-monitoring.yml b/testsuite/performance/docker-compose-monitoring.yml
index a93d55a..308c113 100644
--- a/testsuite/performance/docker-compose-monitoring.yml
+++ b/testsuite/performance/docker-compose-monitoring.yml
@@ -11,6 +11,7 @@ services:
     
     monitoring_influxdb:
         image: influxdb
+        cpuset: ${MONITORING_CPUSET:-1}
         volumes:
         - influx:/var/lib/influxdb
         networks:
@@ -26,6 +27,7 @@ services:
     monitoring_cadvisor:
         build: monitoring/cadvisor
         image: monitoring_cadvisor
+        cpuset: ${MONITORING_CPUSET:-1}
         hostname: '{{.Node.ID}}'
         volumes:
         - /:/rootfs:ro
@@ -50,6 +52,7 @@ services:
     monitoring_grafana:
         build: monitoring/grafana
         image: monitoring_grafana
+        cpuset: ${MONITORING_CPUSET:-1}
         depends_on:
         - monitoring_influxdb
         volumes:
diff --git a/testsuite/performance/keycloak/docker-entrypoint.sh b/testsuite/performance/keycloak/docker-entrypoint.sh
index 9728f98..eb44c09 100644
--- a/testsuite/performance/keycloak/docker-entrypoint.sh
+++ b/testsuite/performance/keycloak/docker-entrypoint.sh
@@ -15,4 +15,3 @@ if [ $KEYCLOAK_USER ] && [ $KEYCLOAK_PASSWORD ]; then
 fi
 
 exec /opt/jboss/keycloak/bin/standalone.sh $PARAMS
-exit $?
diff --git a/testsuite/performance/keycloak/Dockerfile b/testsuite/performance/keycloak/Dockerfile
index 487bad9..91866e8 100644
--- a/testsuite/performance/keycloak/Dockerfile
+++ b/testsuite/performance/keycloak/Dockerfile
@@ -17,26 +17,23 @@ ADD target/keycloak configs/ ./
 ADD *.sh /usr/local/bin/
 
 USER root
-RUN chown -R jboss .; chgrp -R jboss .; 
-RUN chmod -R -v +x /usr/local/bin/
-RUN yum install -y epel-release jq iproute && yum clean all
+RUN chown -R jboss .; chgrp -R jboss .; \
+    chmod -R -v +x /usr/local/bin/ ; \
+    yum install -y epel-release jq iproute && yum clean all
 
 USER jboss
 # install mariadb JDBC driver
-RUN mkdir -p modules/system/layers/base/org/mariadb/jdbc/main; \
-    cd modules/system/layers/base/org/mariadb/jdbc/main; \
-    curl -O http://central.maven.org/maven2/org/mariadb/jdbc/mariadb-java-client/2.0.3/mariadb-java-client-2.0.3.jar
+RUN curl --create-dirs --output modules/system/layers/base/org/mariadb/jdbc/main/mariadb-java-client-2.0.3.jar http://central.maven.org/maven2/org/mariadb/jdbc/mariadb-java-client/2.0.3/mariadb-java-client-2.0.3.jar ; \
+    $JBOSS_HOME/bin/jboss-cli.sh --file=set-keycloak-ds.cli && \
+    $JBOSS_HOME/bin/jboss-cli.sh --file=io-worker-threads.cli && \
+    $JBOSS_HOME/bin/jboss-cli.sh --file=undertow.cli && \
+    $JBOSS_HOME/bin/jboss-cli.sh --file=distributed-cache-owners.cli && \
+    $JBOSS_HOME/bin/jboss-cli.sh --file=modcluster-simple-load-provider.cli && \
+    if [ "$REMOTE_CACHES" == "true" ]; then $JBOSS_HOME/bin/jboss-cli.sh --file=add-remote-cache-stores.cli; fi && \
+    cd $JBOSS_HOME/standalone; rm -rf configuration/standalone_xml_history log data tmp ; \
+    $JBOSS_HOME/bin/add-user.sh -u $DEBUG_USER -p $DEBUG_USER_PASSWORD
+
 ADD module.xml modules/system/layers/base/org/mariadb/jdbc/main/
-# apply configurations
-RUN $JBOSS_HOME/bin/jboss-cli.sh --file=set-keycloak-ds.cli
-RUN $JBOSS_HOME/bin/jboss-cli.sh --file=io-worker-threads.cli
-RUN $JBOSS_HOME/bin/jboss-cli.sh --file=undertow.cli
-RUN $JBOSS_HOME/bin/jboss-cli.sh --file=distributed-cache-owners.cli
-RUN $JBOSS_HOME/bin/jboss-cli.sh --file=modcluster-simple-load-provider.cli
-RUN if [ "$REMOTE_CACHES" == "true" ]; then $JBOSS_HOME/bin/jboss-cli.sh --file=add-remote-cache-stores.cli; fi
-RUN cd $JBOSS_HOME/standalone; rm -rf configuration/standalone_xml_history log data tmp
-
-RUN $JBOSS_HOME/bin/add-user.sh -u $DEBUG_USER -p $DEBUG_USER_PASSWORD
 
 EXPOSE 8080
 EXPOSE 9990
diff --git a/testsuite/performance/prepare-dump.sh b/testsuite/performance/prepare-dump.sh
index 9034e39..23bd99c 100755
--- a/testsuite/performance/prepare-dump.sh
+++ b/testsuite/performance/prepare-dump.sh
@@ -4,13 +4,7 @@ GATLING_HOME=$DIRNAME/tests
 
 if [ -z "$DATASET" ]; then
     echo "This script requires DATASET env variable to be set"
-    echo 1
-fi
-
-./prepare-data.sh $@
-if [ $? -ne 0 ]; then
-    echo "Failed! See log file for details."
-    exit $?
+    exit 1
 fi
 
 echo "Exporting dump file"
diff --git a/testsuite/performance/README.log-tool.md b/testsuite/performance/README.log-tool.md
index 0dac57f..07c23cf 100644
--- a/testsuite/performance/README.log-tool.md
+++ b/testsuite/performance/README.log-tool.md
@@ -6,7 +6,7 @@ Perform the usual test run:
 ```
 mvn verify -Pteardown
 mvn verify -Pprovision
-mvn verify -Pimport-data -Ddataset=100users -Dimport.workers=10 -DhashIterations=100
+mvn verify -Pgenerate-data -Ddataset=100users -Dimport.workers=10 -DhashIterations=100
 mvn verify -Ptest -Ddataset=100users -DrunUsers=200 -DrampUpPeriod=10 -DuserThinkTime=0 -DbadLoginAttempts=1 -DrefreshTokenCount=1 -DnumOfIterations=3
 ```
 
diff --git a/testsuite/performance/README.md b/testsuite/performance/README.md
index f6d1af0..3eadbcc 100644
--- a/testsuite/performance/README.md
+++ b/testsuite/performance/README.md
@@ -24,7 +24,7 @@ mvn clean install
 
 # Make sure your Docker daemon is running THEN
 mvn verify -Pprovision
-mvn verify -Pimport-data -Ddataset=100u -DnumOfWorkers=10 -DhashIterations=100
+mvn verify -Pgenerate-data -Ddataset=100u -DnumOfWorkers=10 -DhashIterations=100
 mvn verify -Ptest -Ddataset=100u -DrunUsers=200 -DrampUpPeriod=10 -DuserThinkTime=0 -DbadLoginAttempts=1 -DrefreshTokenCount=1 -DnumOfIterations=3
 
 ```
@@ -38,7 +38,7 @@ mvn verify -Pteardown
 
 You can perform all phases in a single run:
 ```
-mvn verify -Pprovision,import-data,test,teardown -Ddataset=100u -DnumOfWorkers=10 -DhashIterations=100 -DrunUsers=200 -DrampUpPeriod=10
+mvn verify -Pprovision,generate-data,test,teardown -Ddataset=100u -DnumOfWorkers=10 -DhashIterations=100 -DrunUsers=200 -DrampUpPeriod=10
 ```
 Note: The order in which maven profiles are listed does not determine the order in which profile related plugins are executed. `teardown` profile always executes last.
 
@@ -68,25 +68,26 @@ Provisioning/teardown is performed via `docker-compose` tool. More details in [R
 
 ## Testing
 
-### Import Data
+### Generate Test Data
 
-Usage: `mvn verify -Pimport-data[,cluster] [-Ddataset=DATASET] [-D<dataset.property>=<value>]`.
+Usage: `mvn verify -Pgenerate-data[,cluster] [-Ddataset=DATASET] [-Dexport-dump] [-D<dataset.property>=<value>]`.
 
 Dataset properties are loaded from `datasets/${dataset}.properties` file. Individual properties can be overriden by specifying `-D` params.
 
 Dataset data is first generated as a .json file, and then imported into Keycloak via Admin Client REST API.
 
 #### Examples:
-- `mvn verify -Pimport-data` - import default dataset
-- `mvn verify -Pimport-data -DusersPerRealm=5` - import default dataset, override the `usersPerRealm` property
-- `mvn verify -Pimport-data -Ddataset=100u` - import `100u` dataset
-- `mvn verify -Pimport-data -Ddataset=100r/default` - import dataset from `datasets/100r/default.properties`
+- `mvn verify -Pgenerate-data` - generate default dataset
+- `mvn verify -Pgenerate-data -DusersPerRealm=5` - generate default dataset, override the `usersPerRealm` property
+- `mvn verify -Pgenerate-data -Ddataset=100u` - generate `100u` dataset
+- `mvn verify -Pgenerate-data -Ddataset=100r/default` - generate dataset based on `datasets/100r/default.properties`
 
 The data can also be exported from the database, and stored locally as `datasets/${dataset}.sql.gz`
 `DATASET=100u ./prepare-dump.sh`
 
-If there is a data dump file available then -Pimport-dump can be used to import the data directly into the database, 
-by-passing Keycloak server completely.
+To speed up dataset initialization part, it is possible to pass `-Dexport-dump` option to have the generated dataset
+exported right after it has been generated. Then, if there is a data dump file available then `-Pimport-dump` 
+can be used to import the data directly into the database, bypassing Keycloak server completely.
 
 Usage: `mvn verify -Pimport-dump [-Ddataset=DATASET]`
 
@@ -98,7 +99,7 @@ Usage: `mvn verify -Pimport-dump [-Ddataset=DATASET]`
 
 Usage: `mvn verify -Ptest[,cluster] [-DrunUsers=N] [-DrampUpPeriod=SECONDS] [-DnumOfIterations=N] [-Ddataset=DATASET] [-D<dataset.property>=<value>]* [-D<test.property>=<value>]* `.
 
-_*Note:* The same dataset properties which were used for data import should be supplied to the `test` phase._
+_*Note:* The same dataset properties which were used for data generation/import should be supplied to the `test` phase._
 
 The default test `keycloak.DefaultSimulation` takes the following additional properties:
 
@@ -145,13 +146,13 @@ To view monitoring dashboard open Grafana UI at: `http://localhost:3000/dashboar
 
 ### Single-node
 
-- Provision single node of KC + DB, import data, run test, and tear down the provisioned system:
+- Provision single node of KC + DB, generate data, run test, and tear down the provisioned system:
 
-    `mvn verify -Pprovision,import-data,test,teardown -Ddataset=100u -DrunUsers=100`
+    `mvn verify -Pprovision,generate-data,test,teardown -Ddataset=100u -DrunUsers=100`
 
-- Provision single node of KC + DB, import data, no test, no teardown:
+- Provision single node of KC + DB, generate data, no test, no teardown:
 
-    `mvn verify -Pprovision,import-data -Ddataset=100u`
+    `mvn verify -Pprovision,generate-data -Ddataset=100u`
 
 - Run test against provisioned system using 100 concurrent users ramped up over 10 seconds, then tear it down:
 
@@ -159,13 +160,13 @@ To view monitoring dashboard open Grafana UI at: `http://localhost:3000/dashboar
 
 ### Cluster
 
-- Provision a 1-node KC cluster + DB, import data, run test against the provisioned system, then tear it down:
+- Provision a 1-node KC cluster + DB, generate data, run test against the provisioned system, then tear it down:
 
-    `mvn verify -Pprovision,cluster,import-data,test,teardown -Ddataset=100u -DrunUsers=100`
+    `mvn verify -Pprovision,cluster,generate-data,test,teardown -Ddataset=100u -DrunUsers=100`
 
-- Provision a 2-node KC cluster + DB, import data, run test against the provisioned system, then tear it down:
+- Provision a 2-node KC cluster + DB, generate data, run test against the provisioned system, then tear it down:
 
-    `mvn verify -Pprovision,cluster,import-data,test,teardown -Dkeycloak.scale=2 -DusersPerRealm=200 -DrunUsers=200`
+    `mvn verify -Pprovision,cluster,generate-data,test,teardown -Dkeycloak.scale=2 -DusersPerRealm=200 -DrunUsers=200`
 
 
 ## Developing tests in IntelliJ IDEA
diff --git a/testsuite/performance/README.provisioning-parameters.md b/testsuite/performance/README.provisioning-parameters.md
index bee8ba4..eaea691 100644
--- a/testsuite/performance/README.provisioning-parameters.md
+++ b/testsuite/performance/README.provisioning-parameters.md
@@ -4,7 +4,7 @@
 
 | Category    | Setting                       | Property                           | Default value                                                    |
 |-------------|-------------------------------|------------------------------------|------------------------------------------------------------------|
-| JVM         | Memory settings               | `keycloak.jvm.memory`              | -Xms64m -Xmx512m -XX:MetaspaceSize=96M -XX:MaxMetaspaceSize=256m |
+| JVM         | Memory settings               | `keycloak.jvm.memory`              | -Xms64m -Xmx2g -XX:MetaspaceSize=96M -XX:MaxMetaspaceSize=256m   |
 | Undertow    | HTTP Listener max connections | `keycloak.http.max-connections`    | 500                                                              |
 |             | AJP Listener max connections  | `keycloak.ajp.max-connections`     | 500                                                              |
 | IO          | Worker IO thread pool         | `keycloak.worker.io-threads`       | 2                                                                |
@@ -29,35 +29,20 @@
 |-------------|-------------------------------|-------------------------|-----------------------------------------------------------------------------------------|
 | JVM         | Memory settings               | `infinispan.jvm.memory` | -Xms64m -Xmx512m -XX:MetaspaceSize=96M -XX:MaxMetaspaceSize=256m -XX:+DisableExplicitGC |
 
-## CPUs
-
-At the moment it is not possible to dynamically parametrize the number of CPUs for a service via Maven properties or environment variables.
-
-To change the default value (`cpus: 1`) it is necessary to edit the Docker Compose file.
-
-
-### Example: Keycloak service using 2 CPU cores
-
-`docker-compose.yml` and `docker-compose-cluster.yml`:
-```
-services:
-    ...
-    keycloak:
-        ...
-        cpus: 2
-        ...
-```
-
-`docker-compose-crossdc.yml`:
-```
-services:
-    ...
-    keycloak_dc1:
-        ...
-        cpus: 2
-        ...
-    keycloak_dc2:
-        ...
-        cpus: 2
-        ...
-```
+## Docker settings
+
+By default, there are 4 CPU cores allocated: core 0 for monitoring, core 1 for database (MariaDB), and cores 2 and 3 for Keycloak server.
+Default memory limits for database and Keycloak server are 2g. The `cpuset` and `memlimit` parameters set here are set to `cpuset` and
+`mem_limit` parameters of docker-compose configuration. See docker-compose documentation for meaning of the values. How to set the parameters
+correctly depends on number of factors - number of cpu cores, NUMA, available memory etc., hence it is out of scope of this document.
+
+| Container   | Setting                       | Property                        | Default value                                         |
+|-------------|-------------------------------|---------------------------------|-------------------------------------------------------|
+| Keycloak    | Allocated CPUs                | `keycloak.docker.cpuset`        | 2-3                                                   |
+|             | Allocated CPUs for DC1        | `keycloak.dc1.docker.cpuset`    | 2-3                                                   |
+|             | Allocated CPUs for DC2        | `keycloak.dc2.docker.cpuset`    | 2-3                                                   |
+|             | Available memory              | `keycloak.docker.memlimit`      | 2g                                                    |
+| MariaDB     | Allocated CPUs                | `db.docker.cpuset`              | 1                                                     |
+|             | Available memory              | `db.docker.memlimit`            | 2g                                                    |
+| Monitoring  | Allocated CPUs                | `monitoring.docker.cpuset`      | 0                                                     |
+
diff --git a/testsuite/performance/tests/pom.xml b/testsuite/performance/tests/pom.xml
index 64d9bfa..150c5fd 100644
--- a/testsuite/performance/tests/pom.xml
+++ b/testsuite/performance/tests/pom.xml
@@ -39,7 +39,7 @@
         <keycloak.server.uris>http://localhost:8080/auth</keycloak.server.uris>
         <db.url>jdbc:mariadb://keycloak:keycloak@localhost:3306/keycloak</db.url>
 
-        <keycloak.jvm.memory>-Xms64m -Xmx512m -XX:MetaspaceSize=96M -XX:MaxMetaspaceSize=256m</keycloak.jvm.memory>
+        <keycloak.jvm.memory>-Xms64m -Xmx2g -XX:MetaspaceSize=96M -XX:MaxMetaspaceSize=256m</keycloak.jvm.memory>
         <keycloak.http.max-connections>500</keycloak.http.max-connections>
         <keycloak.ajp.max-connections>500</keycloak.ajp.max-connections>
         <keycloak.worker.io-threads>2</keycloak.worker.io-threads>
@@ -48,14 +48,25 @@
         <keycloak.ds.max-pool-size>100</keycloak.ds.max-pool-size>
         <keycloak.ds.pool-prefill>true</keycloak.ds.pool-prefill>
         <keycloak.ds.ps-cache-size>100</keycloak.ds.ps-cache-size>
-        
+
         <keycloak-lb.jvm.memory>-Xms64m -Xmx512m -XX:MetaspaceSize=96M -XX:MaxMetaspaceSize=256m</keycloak-lb.jvm.memory>
         <keycloak-lb.http.max-connections>500</keycloak-lb.http.max-connections>
         <keycloak-lb.worker.io-threads>2</keycloak-lb.worker.io-threads>
         <keycloak-lb.worker.task-max-threads>16</keycloak-lb.worker.task-max-threads>
-        
+
+        <!-- Docker-related properties -->
+        <db.docker.cpuset>1</db.docker.cpuset>
+        <keycloak.docker.cpuset>2-3</keycloak.docker.cpuset>
+        <keycloak.dc1.docker.cpuset>2</keycloak.dc1.docker.cpuset>
+        <keycloak.dc2.docker.cpuset>3</keycloak.dc2.docker.cpuset>
+        <monitoring.docker.cpuset>0</monitoring.docker.cpuset>
+
+        <db.docker.memlimit>2g</db.docker.memlimit>
+        <keycloak.docker.memlimit>2g</keycloak.docker.memlimit>
+        <!-- End of docker-related properties -->
+
         <infinispan.jvm.memory>-Xms64m -Xmx512m -XX:MetaspaceSize=96M -XX:MaxMetaspaceSize=256m -XX:+DisableExplicitGC</infinispan.jvm.memory>
-        
+
         <dataset>default</dataset>
         <numOfWorkers>1</numOfWorkers>
 
@@ -321,6 +332,13 @@
                                     <commandlineArgs>-f ${compose.file} up -d --build ${compose.up.params}</commandlineArgs>
                                     <environmentVariables>
                                         <KEYCLOAK_VERSION>${project.version}</KEYCLOAK_VERSION>
+
+                                        <KEYCLOAK_CPUSET>${keycloak.docker.cpuset}</KEYCLOAK_CPUSET>
+                                        <KEYCLOAK_DC1_CPUSET>${keycloak.dc1.docker.cpuset}</KEYCLOAK_DC1_CPUSET>
+                                        <KEYCLOAK_DC2_CPUSET>${keycloak.dc2.docker.cpuset}</KEYCLOAK_DC2_CPUSET>
+                                        <KEYCLOAK_MEMLIMIT>${keycloak.docker.memlimit}</KEYCLOAK_MEMLIMIT>
+                                        <DB_CPUSET>${db.docker.cpuset}</DB_CPUSET>
+                                        <DB_MEMLIMIT>${db.docker.memlimit}</DB_MEMLIMIT>
                                         
                                         <KEYCLOAK_JVM_MEMORY>${keycloak.jvm.memory}</KEYCLOAK_JVM_MEMORY>
                                         <KEYCLOAK_HTTP_MAX_CONNECTIONS>${keycloak.http.max-connections}</KEYCLOAK_HTTP_MAX_CONNECTIONS>
@@ -381,7 +399,7 @@
         </profile>
         
         <profile>
-            <id>import-data</id>
+            <id>generate-data</id>
             <build>
                 <plugins>
                     <plugin>
@@ -463,6 +481,43 @@
         </profile>
 
         <profile>
+            <id>export-dump-after-generation</id>
+
+            <activation>
+                <activeByDefault>false</activeByDefault>
+                <property>
+                    <name>export-dump</name>
+                </property>
+            </activation>
+
+            <build>
+                <plugins>
+                    <plugin>
+                        <groupId>org.codehaus.mojo</groupId>
+                        <artifactId>exec-maven-plugin</artifactId>
+                        <executions>
+                            <execution>
+                                <id>export-dump</id>
+                                <phase>pre-integration-test</phase>
+                                <goals>
+                                    <goal>exec</goal>
+                                </goals>
+                                <configuration>
+                                    <workingDirectory>${project.basedir}/..</workingDirectory>
+                                    <executable>./prepare-dump.sh</executable>
+
+                                    <environmentVariables>
+                                        <DATASET>${dataset}</DATASET>
+                                    </environmentVariables>
+                                </configuration>
+                            </execution>
+                        </executions>
+                    </plugin>
+                </plugins>
+            </build>
+        </profile>
+
+        <profile>
             <id>import-dump</id>
             <build>
                 <plugins>
@@ -581,6 +636,9 @@
                                     <workingDirectory>${project.basedir}/..</workingDirectory>
                                     <executable>docker-compose</executable>
                                     <commandlineArgs>-f docker-compose-monitoring.yml up -d --build</commandlineArgs>
+                                    <environmentVariables>
+                                        <MONITORING_CPUSET>${monitoring.docker.cpuset}</MONITORING_CPUSET>
+                                    </environmentVariables>
                                 </configuration>
                             </execution>
                         </executions>
diff --git a/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties b/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties
index 224fdaa..3fa92bb 100644
--- a/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties
+++ b/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties
@@ -1236,7 +1236,7 @@ authz-add-aggregated-policy=Add Aggregated Policy
 authz-add-group-policy=Add Group Policy
 authz-no-groups-assigned=No groups assigned.
 authz-policy-group-claim=Groups Claim
-authz-policy-group-claim.tooltip=A claim to use as the source for user’s group. If the claim is present it must be an array of strings.
+authz-policy-group-claim.tooltip=A claim to use as the source for user's group. If the claim is present it must be an array of strings.
 authz-policy-group-groups.tooltip=Specifies the groups allowed by this policy.
 
 # Authz Permission List
@@ -1358,7 +1358,7 @@ view-authz-client-scope-description=Policies that decide if an admin can view th
 map-roles-authz-client-scope-description=Policies that decide if an admin can map roles defined by this client
 map-roles-client-scope-authz-client-scope-description=Policies that decide if an admin can apply roles defined by this client to the client scope of another client
 map-roles-composite-authz-client-scope-description=Policies that decide if an admin can apply roles defined by this client as a composite to another role
-map-role-authz-role-scope-description=Policies that decide if an admin can map role this role to a user or group
+map-role-authz-role-scope-description=Policies that decide if an admin can map this role to a user or group
 map-role-client-scope-authz-role-scope-description=Policies that decide if an admin can apply this role to the client scope of a client
 map-role-composite-authz-role-scope-description=Policies that decide if an admin can apply this role as a composite to another role
 manage-group-membership-authz-users-scope-description=Policies that decide if an admin can manage group membership for all users in the realm.  This is used in conjunction with specific group policy
diff --git a/themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-client.html b/themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-client.html
index 0c532a6..aa91fe8 100755
--- a/themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-client.html
+++ b/themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-client.html
@@ -26,7 +26,7 @@
             data-ng-show="serverInfo.featureEnabled('AUTHORIZATION') && !disableAuthorizationTab && client.authorizationServicesEnabled">
             <a href="#/realms/{{realm.realm}}/clients/{{client.id}}/authz/resource-server">{{:: 'authz-authorization' |
                 translate}}</a></li>
-        <li ng-class="{active: path[4] == 'revocation'}" data-ng-show="client.protocol != 'docker-v2'"><a
+        <li ng-class="{active: path[4] == 'revocation'}" data-ng-show="client.protocol != 'docker-v2' && client.protocol != 'saml'"><a
                 href="#/realms/{{realm.realm}}/clients/{{client.id}}/revocation">{{:: 'revocation' | translate}}</a>
         </li>
     <!--    <li ng-class="{active: path[4] == 'identity-provider'}" data-ng-show="realm.identityFederationEnabled"><a href="#/realms/{{realm.realm}}/clients/{{client.id}}/identity-provider">Identity Provider</a></li> -->