keycloak-aplcache

KEYCLOAK-4626 AuthenticationSessions - brokering works.

3/27/2017 5:41:36 PM

Changes

Details

diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/AuthenticationSessionAdapter.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/AuthenticationSessionAdapter.java
index d2387cc..202fe5c 100644
--- a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/AuthenticationSessionAdapter.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/AuthenticationSessionAdapter.java
@@ -24,6 +24,7 @@ import java.util.Map;
 import java.util.Set;
 
 import org.infinispan.Cache;
+import org.keycloak.common.util.Time;
 import org.keycloak.models.ClientModel;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.RealmModel;
@@ -142,36 +143,42 @@ public class AuthenticationSessionAdapter implements AuthenticationSessionModel 
     }
 
     @Override
-    public String getNote(String name) {
-        return entity.getNotes() != null ? entity.getNotes().get(name) : null;
+    public String getClientNote(String name) {
+        return entity.getClientNotes() != null ? entity.getClientNotes().get(name) : null;
     }
 
     @Override
-    public void setNote(String name, String value) {
-        if (entity.getNotes() == null) {
-            entity.setNotes(new HashMap<String, String>());
+    public void setClientNote(String name, String value) {
+        if (entity.getClientNotes() == null) {
+            entity.setClientNotes(new HashMap<>());
         }
-        entity.getNotes().put(name, value);
+        entity.getClientNotes().put(name, value);
         update();
     }
 
     @Override
-    public void removeNote(String name) {
-        if (entity.getNotes() != null) {
-            entity.getNotes().remove(name);
+    public void removeClientNote(String name) {
+        if (entity.getClientNotes() != null) {
+            entity.getClientNotes().remove(name);
         }
         update();
     }
 
     @Override
-    public Map<String, String> getNotes() {
-        if (entity.getNotes() == null || entity.getNotes().isEmpty()) return Collections.emptyMap();
+    public Map<String, String> getClientNotes() {
+        if (entity.getClientNotes() == null || entity.getClientNotes().isEmpty()) return Collections.emptyMap();
         Map<String, String> copy = new HashMap<>();
-        copy.putAll(entity.getNotes());
+        copy.putAll(entity.getClientNotes());
         return copy;
     }
 
     @Override
+    public void clearClientNotes() {
+        entity.setClientNotes(new HashMap<>());
+        update();
+    }
+
+    @Override
     public String getAuthNote(String name) {
         return entity.getAuthNotes() != null ? entity.getAuthNotes().get(name) : null;
     }
@@ -286,4 +293,21 @@ public class AuthenticationSessionAdapter implements AuthenticationSessionModel 
         else entity.setAuthUserId(user.getId());
         update();
     }
+
+    @Override
+    public void updateClient(ClientModel client) {
+        entity.setClientUuid(client.getId());
+        update();
+    }
+
+    @Override
+    public void restartSession(RealmModel realm, ClientModel client) {
+        String id = entity.getId();
+        entity = new AuthenticationSessionEntity();
+        entity.setId(id);
+        entity.setRealm(realm.getId());
+        entity.setClientUuid(client.getId());
+        entity.setTimestamp(Time.currentTime());
+        update();
+    }
 }
diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/AuthenticationSessionEntity.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/AuthenticationSessionEntity.java
index dad05e0..0b99225 100644
--- a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/AuthenticationSessionEntity.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/AuthenticationSessionEntity.java
@@ -41,7 +41,7 @@ public class AuthenticationSessionEntity extends SessionEntity {
     private Map<String, AuthenticationSessionModel.ExecutionStatus> executionStatus  = new HashMap<>();;
     private String protocol;
 
-    private Map<String, String> notes;
+    private Map<String, String> clientNotes;
     private Map<String, String> authNotes;
     private Set<String> requiredActions  = new HashSet<>();
     private Map<String, String> userSessionNotes;
@@ -118,12 +118,12 @@ public class AuthenticationSessionEntity extends SessionEntity {
         this.protocol = protocol;
     }
 
-    public Map<String, String> getNotes() {
-        return notes;
+    public Map<String, String> getClientNotes() {
+        return clientNotes;
     }
 
-    public void setNotes(Map<String, String> notes) {
-        this.notes = notes;
+    public void setClientNotes(Map<String, String> clientNotes) {
+        this.clientNotes = clientNotes;
     }
 
     public Set<String> getRequiredActions() {
diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanAuthenticationSessionProvider.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanAuthenticationSessionProvider.java
index 9e021db..1e23524 100644
--- a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanAuthenticationSessionProvider.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanAuthenticationSessionProvider.java
@@ -31,7 +31,6 @@ import org.keycloak.models.sessions.infinispan.entities.AuthenticationSessionEnt
 import org.keycloak.models.sessions.infinispan.stream.AuthenticationSessionPredicate;
 import org.keycloak.models.utils.KeycloakModelUtils;
 import org.keycloak.models.utils.RealmInfoUtil;
-import org.keycloak.services.util.CookieHelper;
 import org.keycloak.sessions.AuthenticationSessionModel;
 import org.keycloak.sessions.AuthenticationSessionProvider;
 
@@ -46,8 +45,6 @@ public class InfinispanAuthenticationSessionProvider implements AuthenticationSe
     private final Cache<String, AuthenticationSessionEntity> cache;
     protected final InfinispanKeycloakTransaction tx;
 
-    public static final String AUTH_SESSION_ID = "AUTH_SESSION_ID";
-
     public InfinispanAuthenticationSessionProvider(KeycloakSession session, Cache<String, AuthenticationSessionEntity> cache) {
         this.session = session;
         this.cache = cache;
@@ -56,11 +53,14 @@ public class InfinispanAuthenticationSessionProvider implements AuthenticationSe
         session.getTransactionManager().enlistAfterCompletion(tx);
     }
 
-
     @Override
-    public AuthenticationSessionModel createAuthenticationSession(RealmModel realm, ClientModel client, boolean browser) {
+    public AuthenticationSessionModel createAuthenticationSession(RealmModel realm, ClientModel client) {
         String id = KeycloakModelUtils.generateId();
+        return createAuthenticationSession(id, realm, client);
+    }
 
+    @Override
+    public AuthenticationSessionModel createAuthenticationSession(String id, RealmModel realm, ClientModel client) {
         AuthenticationSessionEntity entity = new AuthenticationSessionEntity();
         entity.setId(id);
         entity.setRealm(realm.getId());
@@ -69,10 +69,6 @@ public class InfinispanAuthenticationSessionProvider implements AuthenticationSe
 
         tx.put(cache, id, entity);
 
-        if (browser) {
-            setBrowserCookie(id, realm);
-        }
-
         AuthenticationSessionAdapter wrap = wrap(realm, entity);
         return wrap;
     }
@@ -82,28 +78,17 @@ public class InfinispanAuthenticationSessionProvider implements AuthenticationSe
     }
 
     @Override
-    public String getCurrentAuthenticationSessionId(RealmModel realm) {
-        return getIdFromBrowserCookie();
-    }
-
-    @Override
-    public AuthenticationSessionModel getCurrentAuthenticationSession(RealmModel realm) {
-        String authSessionId = getIdFromBrowserCookie();
-        return authSessionId==null ? null : getAuthenticationSession(realm, authSessionId);
-    }
-
-    @Override
     public AuthenticationSessionModel getAuthenticationSession(RealmModel realm, String authenticationSessionId) {
         AuthenticationSessionEntity entity = getAuthenticationSessionEntity(realm, authenticationSessionId);
         return wrap(realm, entity);
     }
 
     private AuthenticationSessionEntity getAuthenticationSessionEntity(RealmModel realm, String authSessionId) {
-        AuthenticationSessionEntity entity = cache.get(authSessionId);
+        // Chance created in this transaction
+        AuthenticationSessionEntity entity = tx.get(cache, authSessionId);
 
-        // Chance created in this transaction TODO:mposolda should it be opposite and rather look locally first? Check performance...
         if (entity == null) {
-            entity = tx.get(cache, authSessionId);
+            entity = cache.get(authSessionId);
         }
 
         return entity;
@@ -156,28 +141,4 @@ public class InfinispanAuthenticationSessionProvider implements AuthenticationSe
 
     }
 
-    // COOKIE STUFF
-
-    protected void setBrowserCookie(String authSessionId, RealmModel realm) {
-        String cookiePath = CookieHelper.getRealmCookiePath(realm);
-        boolean sslRequired = realm.getSslRequired().isRequired(session.getContext().getConnection());
-        CookieHelper.addCookie(AUTH_SESSION_ID, authSessionId, cookiePath, null, null, -1, sslRequired, true);
-
-        // TODO trace with isTraceEnabled
-        log.infof("Set AUTH_SESSION_ID cookie with value %s", authSessionId);
-    }
-
-    protected String getIdFromBrowserCookie() {
-        String cookieVal = CookieHelper.getCookieValue(AUTH_SESSION_ID);
-
-        if (log.isTraceEnabled()) {
-            if (cookieVal != null) {
-                log.tracef("Found AUTH_SESSION_ID cookie with value %s", cookieVal);
-            } else {
-                log.tracef("Not found AUTH_SESSION_ID cookie");
-            }
-        }
-
-        return cookieVal;
-    }
 }
diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanKeycloakTransaction.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanKeycloakTransaction.java
index c6b30f4..4ac40af 100644
--- a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanKeycloakTransaction.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanKeycloakTransaction.java
@@ -16,6 +16,7 @@
  */
 package org.keycloak.models.sessions.infinispan;
 
+import org.infinispan.context.Flag;
 import org.keycloak.models.KeycloakTransaction;
 
 import java.util.HashMap;
@@ -31,7 +32,7 @@ public class InfinispanKeycloakTransaction implements KeycloakTransaction {
     private final static Logger log = Logger.getLogger(InfinispanKeycloakTransaction.class);
 
     public enum CacheOperation {
-        ADD, REMOVE, REPLACE
+        ADD, REMOVE, REPLACE, ADD_IF_ABSENT // ADD_IF_ABSENT throws an exception if there is existing value
     }
 
     private boolean active;
@@ -79,7 +80,18 @@ public class InfinispanKeycloakTransaction implements KeycloakTransaction {
         if (tasks.containsKey(taskKey)) {
             throw new IllegalStateException("Can't add session: task in progress for session");
         } else {
-            tasks.put(taskKey, new CacheTask(cache, CacheOperation.ADD, key, value));
+            tasks.put(taskKey, new CacheTask<>(cache, CacheOperation.ADD, key, value));
+        }
+    }
+
+    public <K, V> void putIfAbsent(Cache<K, V> cache, K key, V value) {
+        log.tracev("Adding cache operation: {0} on {1}", CacheOperation.ADD_IF_ABSENT, key);
+
+        Object taskKey = getTaskKey(cache, key);
+        if (tasks.containsKey(taskKey)) {
+            throw new IllegalStateException("Can't add session: task in progress for session");
+        } else {
+            tasks.put(taskKey, new CacheTask<>(cache, CacheOperation.ADD_IF_ABSENT, key, value));
         }
     }
 
@@ -91,6 +103,7 @@ public class InfinispanKeycloakTransaction implements KeycloakTransaction {
         if (current != null) {
             switch (current.operation) {
                 case ADD:
+                case ADD_IF_ABSENT:
                 case REPLACE:
                     current.value = value;
                     return;
@@ -98,7 +111,7 @@ public class InfinispanKeycloakTransaction implements KeycloakTransaction {
                     return;
             }
         } else {
-            tasks.put(taskKey, new CacheTask(cache, CacheOperation.REPLACE, key, value));
+            tasks.put(taskKey, new CacheTask<>(cache, CacheOperation.REPLACE, key, value));
         }
     }
 
@@ -106,7 +119,7 @@ public class InfinispanKeycloakTransaction implements KeycloakTransaction {
         log.tracev("Adding cache operation: {0} on {1}", CacheOperation.REMOVE, key);
 
         Object taskKey = getTaskKey(cache, key);
-        tasks.put(taskKey, new CacheTask(cache, CacheOperation.REMOVE, key, null));
+        tasks.put(taskKey, new CacheTask<>(cache, CacheOperation.REMOVE, key, null));
     }
 
     // This is for possibility to lookup for session by id, which was created in this transaction
@@ -116,12 +129,16 @@ public class InfinispanKeycloakTransaction implements KeycloakTransaction {
         if (current != null) {
             switch (current.operation) {
                 case ADD:
+                case ADD_IF_ABSENT:
                 case REPLACE:
                     return current.value;
+                case REMOVE:
+                    return null;
             }
         }
 
-        return null;
+        // Should we have per-transaction cache for lookups?
+        return cache.get(key);
     }
 
     private static <K, V> Object getTaskKey(Cache<K, V> cache, K key) {
@@ -152,15 +169,28 @@ public class InfinispanKeycloakTransaction implements KeycloakTransaction {
 
             switch (operation) {
                 case ADD:
-                    cache.put(key, value);
+                    decorateCache().put(key, value);
                     break;
                 case REMOVE:
-                    cache.remove(key);
+                    decorateCache().remove(key);
                     break;
                 case REPLACE:
-                    cache.replace(key, value);
+                    decorateCache().replace(key, value);
+                    break;
+                case ADD_IF_ABSENT:
+                    V existing = cache.putIfAbsent(key, value);
+                    if (existing != null) {
+                        throw new IllegalStateException("IllegalState. There is already existing value in cache for key " + key);
+                    }
                     break;
             }
         }
+
+
+        // Ignore return values. Should have better performance within cluster / cross-dc env
+        private Cache<K, V> decorateCache() {
+            return cache.getAdvancedCache()
+                    .withFlags(Flag.IGNORE_RETURN_VALUES, Flag.SKIP_REMOTE_LOOKUP);
+        }
     }
 }
\ No newline at end of file
diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProvider.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProvider.java
index 72ea8be..e87347e 100755
--- a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProvider.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProvider.java
@@ -120,9 +120,7 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
     }
 
     @Override
-    public UserSessionModel createUserSession(RealmModel realm, UserModel user, String loginUsername, String ipAddress, String authMethod, boolean rememberMe, String brokerSessionId, String brokerUserId) {
-        String id = KeycloakModelUtils.generateId();
-
+    public UserSessionModel createUserSession(String id, RealmModel realm, UserModel user, String loginUsername, String ipAddress, String authMethod, boolean rememberMe, String brokerSessionId, String brokerUserId) {
         UserSessionEntity entity = new UserSessionEntity();
         entity.setId(id);
         entity.setRealm(realm.getId());
@@ -139,7 +137,7 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
         entity.setStarted(currentTime);
         entity.setLastSessionRefresh(currentTime);
 
-        tx.put(sessionCache, id, entity);
+        tx.putIfAbsent(sessionCache, id, entity);
 
         return wrap(realm, entity, false);
     }
@@ -151,11 +149,10 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
 
     protected ClientSessionModel getClientSession(RealmModel realm, String id, boolean offline) {
         Cache<String, SessionEntity> cache = getCache(offline);
-        ClientSessionEntity entity = (ClientSessionEntity) cache.get(id);
+        ClientSessionEntity entity = (ClientSessionEntity) tx.get(cache, id); // Chance created in this transaction
 
-        // Chance created in this transaction
         if (entity == null) {
-            entity = (ClientSessionEntity) tx.get(cache, id);
+            entity = (ClientSessionEntity) cache.get(id);
         }
 
         return wrap(realm, entity, offline);
@@ -163,11 +160,11 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
 
     @Override
     public ClientSessionModel getClientSession(String id) {
-        ClientSessionEntity entity = (ClientSessionEntity) sessionCache.get(id);
-
         // Chance created in this transaction
+        ClientSessionEntity entity = (ClientSessionEntity) tx.get(sessionCache, id);
+
         if (entity == null) {
-            entity = (ClientSessionEntity) tx.get(sessionCache, id);
+            entity = (ClientSessionEntity) sessionCache.get(id);
         }
 
         if (entity != null) {
@@ -184,11 +181,10 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
 
     protected UserSessionAdapter getUserSession(RealmModel realm, String id, boolean offline) {
         Cache<String, SessionEntity> cache = getCache(offline);
-        UserSessionEntity entity = (UserSessionEntity) cache.get(id);
+        UserSessionEntity entity = (UserSessionEntity) tx.get(cache, id); // Chance created in this transaction
 
-        // Chance created in this transaction
         if (entity == null) {
-            entity = (UserSessionEntity) tx.get(cache, id);
+            entity = (UserSessionEntity) cache.get(id);
         }
 
         return wrap(realm, entity, offline);
@@ -742,11 +738,10 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
     @Override
     public ClientInitialAccessModel getClientInitialAccessModel(RealmModel realm, String id) {
         Cache<String, SessionEntity> cache = getCache(false);
-        ClientInitialAccessEntity entity = (ClientInitialAccessEntity) cache.get(id);
+        ClientInitialAccessEntity entity = (ClientInitialAccessEntity) tx.get(cache, id); // Chance created in this transaction
 
-        // If created in this transaction
         if (entity == null) {
-            entity = (ClientInitialAccessEntity) tx.get(cache, id);
+            entity = (ClientInitialAccessEntity) cache.get(id);
         }
 
         return wrap(realm, entity);
diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/UserSessionAdapter.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/UserSessionAdapter.java
index 245e3e8..d87612a 100755
--- a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/UserSessionAdapter.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/UserSessionAdapter.java
@@ -67,9 +67,11 @@ public class UserSessionAdapter implements UserSessionModel {
         Map<String, ClientLoginSessionEntity> clientSessionEntities = entity.getClientLoginSessions();
         Map<String, AuthenticatedClientSessionModel> result = new HashMap<>();
 
-        clientSessionEntities.forEach((String key, ClientLoginSessionEntity value) -> {
-            result.put(key, new AuthenticatedClientSessionAdapter(value, this, provider, cache));
-        });
+        if (clientSessionEntities != null) {
+            clientSessionEntities.forEach((String key, ClientLoginSessionEntity value) -> {
+                result.put(key, new AuthenticatedClientSessionAdapter(value, this, provider, cache));
+            });
+        }
 
         return Collections.unmodifiableMap(result);
     }
@@ -97,6 +99,11 @@ public class UserSessionAdapter implements UserSessionModel {
     }
 
     @Override
+    public void setUser(UserModel user) {
+        entity.setUser(user.getId());
+    }
+
+    @Override
     public String getLoginUsername() {
         return entity.getLoginUsername();
     }
diff --git a/server-spi/src/main/java/org/keycloak/models/AuthenticatedClientSessionModel.java b/server-spi/src/main/java/org/keycloak/models/AuthenticatedClientSessionModel.java
index d4d2a33..15dc57f 100644
--- a/server-spi/src/main/java/org/keycloak/models/AuthenticatedClientSessionModel.java
+++ b/server-spi/src/main/java/org/keycloak/models/AuthenticatedClientSessionModel.java
@@ -18,6 +18,8 @@
 package org.keycloak.models;
 
 
+import java.util.Map;
+
 import org.keycloak.sessions.CommonClientSessionModel;
 
 /**
@@ -27,4 +29,9 @@ public interface AuthenticatedClientSessionModel extends CommonClientSessionMode
 
     void setUserSession(UserSessionModel userSession);
     UserSessionModel getUserSession();
+
+    public String getNote(String name);
+    public void setNote(String name, String value);
+    public void removeNote(String name);
+    public Map<String, String> getNotes();
 }
diff --git a/server-spi/src/main/java/org/keycloak/models/ClientSessionModel.java b/server-spi/src/main/java/org/keycloak/models/ClientSessionModel.java
index 109709f..12abb10 100755
--- a/server-spi/src/main/java/org/keycloak/models/ClientSessionModel.java
+++ b/server-spi/src/main/java/org/keycloak/models/ClientSessionModel.java
@@ -72,5 +72,10 @@ public interface ClientSessionModel extends CommonClientSessionModel {
 
     public void clearUserSessionNotes();
 
+    public String getNote(String name);
+    public void setNote(String name, String value);
+    public void removeNote(String name);
+    public Map<String, String> getNotes();
+
 
 }
diff --git a/server-spi/src/main/java/org/keycloak/models/UserSessionModel.java b/server-spi/src/main/java/org/keycloak/models/UserSessionModel.java
index 9e1a2b6..0dc2f5c 100755
--- a/server-spi/src/main/java/org/keycloak/models/UserSessionModel.java
+++ b/server-spi/src/main/java/org/keycloak/models/UserSessionModel.java
@@ -66,8 +66,10 @@ public interface UserSessionModel {
     State getState();
     void setState(State state);
 
+    void setUser(UserModel user);
+
     public static enum State {
-        LOGGING_IN, // TODO: Maybe state "LOGGING_IN" is useless now once userSession is attached after requiredActions
+        LOGGING_IN, // TODO:mposolda Maybe state "LOGGING_IN" is useless now once userSession is attached after requiredActions
         LOGGED_IN,
         LOGGING_OUT,
         LOGGED_OUT
diff --git a/server-spi/src/main/java/org/keycloak/models/UserSessionProvider.java b/server-spi/src/main/java/org/keycloak/models/UserSessionProvider.java
index 7925bf7..1afbcba 100755
--- a/server-spi/src/main/java/org/keycloak/models/UserSessionProvider.java
+++ b/server-spi/src/main/java/org/keycloak/models/UserSessionProvider.java
@@ -32,7 +32,7 @@ public interface UserSessionProvider extends Provider {
     ClientSessionModel getClientSession(RealmModel realm, String id);
     ClientSessionModel getClientSession(String id);
 
-    UserSessionModel createUserSession(RealmModel realm, UserModel user, String loginUsername, String ipAddress, String authMethod, boolean rememberMe, String brokerSessionId, String brokerUserId);
+    UserSessionModel createUserSession(String id, RealmModel realm, UserModel user, String loginUsername, String ipAddress, String authMethod, boolean rememberMe, String brokerSessionId, String brokerUserId);
     UserSessionModel getUserSession(RealmModel realm, String id);
     List<UserSessionModel> getUserSessions(RealmModel realm, UserModel user);
     List<UserSessionModel> getUserSessions(RealmModel realm, ClientModel client);
diff --git a/server-spi/src/main/java/org/keycloak/sessions/AuthenticationSessionModel.java b/server-spi/src/main/java/org/keycloak/sessions/AuthenticationSessionModel.java
index e12cf26..9602db0 100644
--- a/server-spi/src/main/java/org/keycloak/sessions/AuthenticationSessionModel.java
+++ b/server-spi/src/main/java/org/keycloak/sessions/AuthenticationSessionModel.java
@@ -20,6 +20,8 @@ package org.keycloak.sessions;
 import java.util.Map;
 import java.util.Set;
 
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.RealmModel;
 import org.keycloak.models.UserModel;
 
 /**
@@ -34,11 +36,11 @@ public interface AuthenticationSessionModel extends CommonClientSessionModel {
 //    public void setUserSession(UserSessionModel userSession);
 
 
-    public Map<String, ExecutionStatus> getExecutionStatus();
-    public void setExecutionStatus(String authenticator, ExecutionStatus status);
-    public void clearExecutionStatus();
-    public UserModel getAuthenticatedUser();
-    public void setAuthenticatedUser(UserModel user);
+    Map<String, ExecutionStatus> getExecutionStatus();
+    void setExecutionStatus(String authenticator, ExecutionStatus status);
+    void clearExecutionStatus();
+    UserModel getAuthenticatedUser();
+    void setAuthenticatedUser(UserModel user);
 
     /**
      * Required actions that are attached to this client session.
@@ -56,26 +58,26 @@ public interface AuthenticationSessionModel extends CommonClientSessionModel {
     void removeRequiredAction(UserModel.RequiredAction action);
 
 
-    /**
-     * These are notes you want applied to the UserSessionModel when the client session is attached to it.
-     *
-     * @param name
-     * @param value
-     */
-    public void setUserSessionNote(String name, String value);
+    // These are notes you want applied to the UserSessionModel when the client session is attached to it.
+    void setUserSessionNote(String name, String value);
+    Map<String, String> getUserSessionNotes();
+    void clearUserSessionNotes();
 
-    /**
-     * These are notes you want applied to the UserSessionModel when the client session is attached to it.
-     *
-     * @return
-     */
-    public Map<String, String> getUserSessionNotes();
+    // These are notes used typically by authenticators and authentication flows. They are cleared when authentication session is restarted
+    String getAuthNote(String name);
+    void setAuthNote(String name, String value);
+    void removeAuthNote(String name);
+    void clearAuthNotes();
 
-    public void clearUserSessionNotes();
+    // These are notes specific to client protocol. They are NOT cleared when authentication session is restarted
+    String getClientNote(String name);
+    void setClientNote(String name, String value);
+    void removeClientNote(String name);
+    Map<String, String> getClientNotes();
+    void clearClientNotes();
 
-    public String getAuthNote(String name);
-    public void setAuthNote(String name, String value);
-    public void removeAuthNote(String name);
-    public void clearAuthNotes();
+    void updateClient(ClientModel client);
 
+    // Will completely restart whole state of authentication session. It will just keep same ID. It will setup it with provided realm and client.
+    void restartSession(RealmModel realm, ClientModel client);
 }
diff --git a/server-spi/src/main/java/org/keycloak/sessions/AuthenticationSessionProvider.java b/server-spi/src/main/java/org/keycloak/sessions/AuthenticationSessionProvider.java
index a3b04d3..42884cc 100644
--- a/server-spi/src/main/java/org/keycloak/sessions/AuthenticationSessionProvider.java
+++ b/server-spi/src/main/java/org/keycloak/sessions/AuthenticationSessionProvider.java
@@ -26,11 +26,10 @@ import org.keycloak.provider.Provider;
  */
 public interface AuthenticationSessionProvider extends Provider {
 
-    AuthenticationSessionModel createAuthenticationSession(RealmModel realm, ClientModel client, boolean browser);
+    // Generates random ID
+    AuthenticationSessionModel createAuthenticationSession(RealmModel realm, ClientModel client);
 
-    String getCurrentAuthenticationSessionId(RealmModel realm);
-
-    AuthenticationSessionModel getCurrentAuthenticationSession(RealmModel realm);
+    AuthenticationSessionModel createAuthenticationSession(String id, RealmModel realm, ClientModel client);
 
     AuthenticationSessionModel getAuthenticationSession(RealmModel realm, String authenticationSessionId);
 
diff --git a/server-spi/src/main/java/org/keycloak/sessions/CommonClientSessionModel.java b/server-spi/src/main/java/org/keycloak/sessions/CommonClientSessionModel.java
index e6a9288..c47a6a5 100644
--- a/server-spi/src/main/java/org/keycloak/sessions/CommonClientSessionModel.java
+++ b/server-spi/src/main/java/org/keycloak/sessions/CommonClientSessionModel.java
@@ -53,25 +53,12 @@ public interface CommonClientSessionModel {
     // TODO: Not needed here...?
     public Set<String> getProtocolMappers();
     public void setProtocolMappers(Set<String> protocolMappers);
-
-    public String getNote(String name);
-    public void setNote(String name, String value);
-    public void removeNote(String name);
-    public Map<String, String> getNotes();
-
+    
     public static enum Action {
         OAUTH_GRANT,
         CODE_TO_TOKEN,
-        VERIFY_EMAIL,
-        UPDATE_PROFILE,
-        CONFIGURE_TOTP,
-        UPDATE_PASSWORD,
-        RECOVER_PASSWORD, // deprecated
         AUTHENTICATE,
-        SOCIAL_CALLBACK,
         LOGGED_OUT,
-        RESET_CREDENTIALS,
-        EXECUTE_ACTIONS,
         REQUIRED_ACTIONS
     }
 
diff --git a/server-spi-private/src/main/java/org/keycloak/authentication/AuthenticationFlowContext.java b/server-spi-private/src/main/java/org/keycloak/authentication/AuthenticationFlowContext.java
index e872565..2159f09 100755
--- a/server-spi-private/src/main/java/org/keycloak/authentication/AuthenticationFlowContext.java
+++ b/server-spi-private/src/main/java/org/keycloak/authentication/AuthenticationFlowContext.java
@@ -100,6 +100,12 @@ public interface AuthenticationFlowContext extends AbstractAuthenticationFlowCon
     void resetFlow();
 
     /**
+     * Reset the current flow to the beginning and restarts it. Allows to add additional listener, which is triggered after flow restarted
+     *
+     */
+    void resetFlow(Runnable afterResetListener);
+
+    /**
      * Fork the current flow.  The client session will be cloned and set to point at the realm's browser login flow.  The Response will be the result
      * of this fork.  The previous flow will still be set at the current execution.  This is used by reset password when it sends an email.
      * It sends an email linking to the current flow and redirects the browser to a new browser login flow.
diff --git a/server-spi-private/src/main/java/org/keycloak/broker/provider/AbstractIdentityProvider.java b/server-spi-private/src/main/java/org/keycloak/broker/provider/AbstractIdentityProvider.java
index 8f57133..9d71b78 100755
--- a/server-spi-private/src/main/java/org/keycloak/broker/provider/AbstractIdentityProvider.java
+++ b/server-spi-private/src/main/java/org/keycloak/broker/provider/AbstractIdentityProvider.java
@@ -23,6 +23,7 @@ import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.UserModel;
 import org.keycloak.models.UserSessionModel;
+import org.keycloak.sessions.AuthenticationSessionModel;
 
 import javax.ws.rs.core.Response;
 import javax.ws.rs.core.UriInfo;
@@ -75,7 +76,7 @@ public abstract class AbstractIdentityProvider<C extends IdentityProviderModel> 
     }
 
     @Override
-    public void attachUserSession(UserSessionModel userSession, ClientSessionModel clientSession, BrokeredIdentityContext context) {
+    public void authenticationFinished(AuthenticationSessionModel authSession, BrokeredIdentityContext context) {
 
     }
 
diff --git a/server-spi-private/src/main/java/org/keycloak/broker/provider/AuthenticationRequest.java b/server-spi-private/src/main/java/org/keycloak/broker/provider/AuthenticationRequest.java
index 863decb..7affd99 100644
--- a/server-spi-private/src/main/java/org/keycloak/broker/provider/AuthenticationRequest.java
+++ b/server-spi-private/src/main/java/org/keycloak/broker/provider/AuthenticationRequest.java
@@ -20,6 +20,7 @@ import org.jboss.resteasy.spi.HttpRequest;
 import org.keycloak.models.ClientSessionModel;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.RealmModel;
+import org.keycloak.sessions.AuthenticationSessionModel;
 
 import javax.ws.rs.core.UriInfo;
 
@@ -34,16 +35,16 @@ public class AuthenticationRequest {
     private final HttpRequest httpRequest;
     private final RealmModel realm;
     private final String redirectUri;
-    private final ClientSessionModel clientSession;
+    private final AuthenticationSessionModel authSession;
 
-    public AuthenticationRequest(KeycloakSession session, RealmModel realm, ClientSessionModel clientSession, HttpRequest httpRequest, UriInfo uriInfo, String state, String redirectUri) {
+    public AuthenticationRequest(KeycloakSession session, RealmModel realm, AuthenticationSessionModel authSession, HttpRequest httpRequest, UriInfo uriInfo, String state, String redirectUri) {
         this.session = session;
         this.realm = realm;
         this.httpRequest = httpRequest;
         this.uriInfo = uriInfo;
         this.state = state;
         this.redirectUri = redirectUri;
-        this.clientSession = clientSession;
+        this.authSession = authSession;
     }
 
     public KeycloakSession getSession() {
@@ -76,7 +77,7 @@ public class AuthenticationRequest {
         return this.redirectUri;
     }
 
-    public ClientSessionModel getClientSession() {
-        return this.clientSession;
+    public AuthenticationSessionModel getAuthenticationSession() {
+        return this.authSession;
     }
 }
diff --git a/server-spi-private/src/main/java/org/keycloak/broker/provider/IdentityProvider.java b/server-spi-private/src/main/java/org/keycloak/broker/provider/IdentityProvider.java
index b14572e..c4f1c5c 100755
--- a/server-spi-private/src/main/java/org/keycloak/broker/provider/IdentityProvider.java
+++ b/server-spi-private/src/main/java/org/keycloak/broker/provider/IdentityProvider.java
@@ -17,7 +17,6 @@
 package org.keycloak.broker.provider;
 
 import org.keycloak.events.EventBuilder;
-import org.keycloak.models.ClientSessionModel;
 import org.keycloak.models.FederatedIdentityModel;
 import org.keycloak.models.IdentityProviderModel;
 import org.keycloak.models.KeycloakSession;
@@ -25,6 +24,7 @@ import org.keycloak.models.RealmModel;
 import org.keycloak.models.UserModel;
 import org.keycloak.models.UserSessionModel;
 import org.keycloak.provider.Provider;
+import org.keycloak.sessions.AuthenticationSessionModel;
 
 import javax.ws.rs.core.Response;
 import javax.ws.rs.core.UriInfo;
@@ -51,7 +51,7 @@ public interface IdentityProvider<C extends IdentityProviderModel> extends Provi
 
 
     void preprocessFederatedIdentity(KeycloakSession session, RealmModel realm, BrokeredIdentityContext context);
-    void attachUserSession(UserSessionModel userSession, ClientSessionModel clientSession, BrokeredIdentityContext context);
+    void authenticationFinished(AuthenticationSessionModel authSession, BrokeredIdentityContext context);
     void importNewUser(KeycloakSession session, RealmModel realm, UserModel user, BrokeredIdentityContext context);
     void updateBrokeredUser(KeycloakSession session, RealmModel realm, UserModel user, BrokeredIdentityContext context);
 
diff --git a/server-spi-private/src/main/java/org/keycloak/events/Details.java b/server-spi-private/src/main/java/org/keycloak/events/Details.java
index 3e72e8e..2483a9b 100755
--- a/server-spi-private/src/main/java/org/keycloak/events/Details.java
+++ b/server-spi-private/src/main/java/org/keycloak/events/Details.java
@@ -48,7 +48,6 @@ public interface Details {
     String CLIENT_SESSION_STATE = "client_session_state";
     String CLIENT_SESSION_HOST = "client_session_host";
     String RESTART_AFTER_TIMEOUT = "restart_after_timeout";
-    String RESTART_REQUESTED = "restart_requested";
 
     String CONSENT = "consent";
     String CONSENT_VALUE_NO_CONSENT_REQUIRED = "no_consent_required"; // No consent is required by client
@@ -64,4 +63,6 @@ public interface Details {
 
     String CLIENT_REGISTRATION_POLICY = "client_registration_policy";
 
+    String EXISTING_USER = "previous_user";
+
 }
diff --git a/server-spi-private/src/main/java/org/keycloak/events/Errors.java b/server-spi-private/src/main/java/org/keycloak/events/Errors.java
index e82421f..ab954bc 100755
--- a/server-spi-private/src/main/java/org/keycloak/events/Errors.java
+++ b/server-spi-private/src/main/java/org/keycloak/events/Errors.java
@@ -37,6 +37,7 @@ public interface Errors {
     String USER_DISABLED = "user_disabled";
     String USER_TEMPORARILY_DISABLED = "user_temporarily_disabled";
     String INVALID_USER_CREDENTIALS = "invalid_user_credentials";
+    String DIFFERENT_USER_AUTHENTICATED = "different_user_authenticated";
 
     String USERNAME_MISSING = "username_missing";
     String USERNAME_IN_USE = "username_in_use";
diff --git a/server-spi-private/src/main/java/org/keycloak/events/EventType.java b/server-spi-private/src/main/java/org/keycloak/events/EventType.java
index f77137f..ea4b1fb 100755
--- a/server-spi-private/src/main/java/org/keycloak/events/EventType.java
+++ b/server-spi-private/src/main/java/org/keycloak/events/EventType.java
@@ -79,6 +79,9 @@ public enum EventType {
     RESET_PASSWORD(true),
     RESET_PASSWORD_ERROR(true),
 
+    RESTART_AUTHENTICATION(true),
+    RESTART_AUTHENTICATION_ERROR(true),
+
     INVALID_SIGNATURE(false),
     INVALID_SIGNATURE_ERROR(false),
     REGISTER_NODE(false),
diff --git a/server-spi-private/src/main/java/org/keycloak/models/session/PersistentUserSessionAdapter.java b/server-spi-private/src/main/java/org/keycloak/models/session/PersistentUserSessionAdapter.java
index 7b14ec1..7ba4bc4 100644
--- a/server-spi-private/src/main/java/org/keycloak/models/session/PersistentUserSessionAdapter.java
+++ b/server-spi-private/src/main/java/org/keycloak/models/session/PersistentUserSessionAdapter.java
@@ -116,6 +116,11 @@ public class PersistentUserSessionAdapter implements UserSessionModel {
     }
 
     @Override
+    public void setUser(UserModel user) {
+        throw new IllegalStateException("Not supported");
+    }
+
+    @Override
     public RealmModel getRealm() {
         return realm;
     }
diff --git a/server-spi-private/src/main/java/org/keycloak/protocol/LoginProtocol.java b/server-spi-private/src/main/java/org/keycloak/protocol/LoginProtocol.java
index 8fc9fd9..569a2c0 100755
--- a/server-spi-private/src/main/java/org/keycloak/protocol/LoginProtocol.java
+++ b/server-spi-private/src/main/java/org/keycloak/protocol/LoginProtocol.java
@@ -23,7 +23,6 @@ import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.UserSessionModel;
 import org.keycloak.provider.Provider;
-import org.keycloak.services.managers.ClientSessionCode;
 import org.keycloak.sessions.AuthenticationSessionModel;
 
 import javax.ws.rs.core.HttpHeaders;
@@ -67,7 +66,7 @@ public interface LoginProtocol extends Provider {
 
     LoginProtocol setEventBuilder(EventBuilder event);
 
-    Response authenticated(UserSessionModel userSession, ClientSessionCode<AuthenticatedClientSessionModel> accessCode);
+    Response authenticated(UserSessionModel userSession, AuthenticatedClientSessionModel clientSession);
 
     Response sendError(AuthenticationSessionModel authSession, Error error);
 
diff --git a/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java b/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java
index 70c876f..e633c70 100755
--- a/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java
+++ b/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java
@@ -44,16 +44,20 @@ import org.keycloak.protocol.LoginProtocol;
 import org.keycloak.protocol.LoginProtocol.Error;
 import org.keycloak.protocol.oidc.TokenManager;
 import org.keycloak.services.ErrorPage;
+import org.keycloak.services.ErrorPageException;
 import org.keycloak.services.ServicesLogger;
 import org.keycloak.services.managers.AuthenticationManager;
+import org.keycloak.services.managers.AuthenticationSessionManager;
 import org.keycloak.services.managers.BruteForceProtector;
 import org.keycloak.services.managers.ClientSessionCode;
 import org.keycloak.services.messages.Messages;
 import org.keycloak.services.resources.LoginActionsService;
 import org.keycloak.services.util.CacheControlUtil;
+import org.keycloak.services.util.PageExpiredRedirect;
 import org.keycloak.sessions.AuthenticationSessionModel;
 
 import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriBuilder;
 import javax.ws.rs.core.UriInfo;
 import java.net.URI;
 import java.util.HashMap;
@@ -69,6 +73,10 @@ public class AuthenticationProcessor {
     public static final String LAST_PROCESSED_EXECUTION = "last.processed.execution";
     public static final String CURRENT_FLOW_PATH = "current.flow.path";
     public static final String FORKED_FROM = "forked.from";
+    public static final String FORWARDED_ERROR_MESSAGE_NOTE = "forwardedErrorMessage";
+
+    public static final String BROKER_SESSION_ID = "broker.session.id";
+    public static final String BROKER_USER_ID = "broker.user.id";
 
     protected static final Logger logger = Logger.getLogger(AuthenticationProcessor.class);
     protected RealmModel realm;
@@ -83,7 +91,7 @@ public class AuthenticationProcessor {
     protected String flowPath;
     protected boolean browserFlow;
     protected BruteForceProtector protector;
-    protected boolean oneActionWasSuccessful;
+    protected Runnable afterResetListener;
     /**
      * This could be an error message forwarded from another authenticator
      */
@@ -509,6 +517,12 @@ public class AuthenticationProcessor {
         }
 
         @Override
+        public void resetFlow(Runnable afterResetListener) {
+            this.status = FlowStatus.FLOW_RESET;
+            AuthenticationProcessor.this.afterResetListener = afterResetListener;
+        }
+
+        @Override
         public void fork() {
             this.status = FlowStatus.FORK;
         }
@@ -558,7 +572,7 @@ public class AuthenticationProcessor {
 
     protected void logSuccess() {
         if (realm.isBruteForceProtected()) {
-            String username = clientSession.getNote(AbstractUsernameFormAuthenticator.ATTEMPTED_USERNAME);
+            String username = authenticationSession.getAuthNote(AbstractUsernameFormAuthenticator.ATTEMPTED_USERNAME);
             // TODO: as above, need to handle non form success
 
             if(username == null) {
@@ -608,6 +622,8 @@ public class AuthenticationProcessor {
                 ForkFlowException reset = (ForkFlowException)e;
                 AuthenticationSessionModel clone = clone(session, authenticationSession);
                 clone.setAction(ClientSessionModel.Action.AUTHENTICATE.name());
+                setAuthenticationSession(clone);
+
                 AuthenticationProcessor processor = new AuthenticationProcessor();
                 processor.setAuthenticationSession(clone)
                         .setFlowPath(LoginActionsService.AUTHENTICATE_PATH)
@@ -701,46 +717,40 @@ public class AuthenticationProcessor {
     }
 
 
-    public Response redirectToFlow(String execution) {
-        logger.info("Redirecting to flow with execution: " + execution);
-        authenticationSession.setAuthNote(LAST_PROCESSED_EXECUTION, execution);
+    public Response redirectToFlow() {
+        URI redirect = new PageExpiredRedirect(session, realm, uriInfo).getLastExecutionUrl(authenticationSession);
+
+        logger.info("Redirecting to URL: " + redirect.toString());
 
-        URI redirect = LoginActionsService.loginActionsBaseUrl(getUriInfo())
-                .path(flowPath)
-                .queryParam("execution", execution).build(getRealm().getName());
         return Response.status(302).location(redirect).build();
 
     }
 
-    public static Response redirectToRequiredActions(KeycloakSession session, RealmModel realm, AuthenticationSessionModel authSession, UriInfo uriInfo) {
-
-        // redirect to non-action url so browser refresh button works without reposting past data
-        ClientSessionCode<AuthenticationSessionModel> accessCode = new ClientSessionCode<>(session, realm, authSession);
-        accessCode.setAction(ClientSessionModel.Action.REQUIRED_ACTIONS.name());
-        authSession.setTimestamp(Time.currentTime());
-
-        URI redirect = LoginActionsService.loginActionsBaseUrl(uriInfo)
-                .path(LoginActionsService.REQUIRED_ACTION)
-                .queryParam(OAuth2Constants.CODE, accessCode.getCode()).build(realm.getName());
-        return Response.status(302).location(redirect).build();
+    public void resetFlow() {
+        resetFlow(authenticationSession, flowPath);
 
+        if (afterResetListener != null) {
+            afterResetListener.run();
+        }
     }
 
-    public static void resetFlow(AuthenticationSessionModel authSession) {
+    public static void resetFlow(AuthenticationSessionModel authSession, String flowPath) {
         logger.debug("RESET FLOW");
         authSession.setTimestamp(Time.currentTime());
         authSession.setAuthenticatedUser(null);
         authSession.clearExecutionStatus();
         authSession.clearUserSessionNotes();
         authSession.clearAuthNotes();
+
+        authSession.setAuthNote(CURRENT_FLOW_PATH, flowPath);
     }
 
     public static AuthenticationSessionModel clone(KeycloakSession session, AuthenticationSessionModel authSession) {
-        AuthenticationSessionModel clone = session.authenticationSessions().createAuthenticationSession(authSession.getRealm(), authSession.getClient(), true);
+        AuthenticationSessionModel clone = new AuthenticationSessionManager(session).createAuthenticationSession(authSession.getRealm(), authSession.getClient(), true);
 
         // Transfer just the client "notes", but not "authNotes"
-        for (Map.Entry<String, String> entry : authSession.getNotes().entrySet()) {
-            clone.setNote(entry.getKey(), entry.getValue());
+        for (Map.Entry<String, String> entry : authSession.getClientNotes().entrySet()) {
+            clone.setClientNote(entry.getKey(), entry.getValue());
         }
 
         clone.setRedirectUri(authSession.getRedirectUri());
@@ -762,7 +772,7 @@ public class AuthenticationProcessor {
         if (!execution.equals(current)) {
             // TODO:mposolda debug
             logger.info("Current execution does not equal executed execution.  Might be a page refresh");
-            return redirectToFlow(current);
+            return new PageExpiredRedirect(session, realm, uriInfo).showPageExpired(authenticationSession);
         }
         UserModel authUser = authenticationSession.getAuthenticatedUser();
         validateUser(authUser);
@@ -770,7 +780,7 @@ public class AuthenticationProcessor {
         if (model == null) {
             logger.debug("Cannot find execution, reseting flow");
             logFailure();
-            resetFlow(authenticationSession);
+            resetFlow();
             return authenticate();
         }
         event.client(authenticationSession.getClient().getClientId())
@@ -826,28 +836,6 @@ public class AuthenticationProcessor {
         return challenge;
     }
 
-    /**
-     * Marks that at least one action was successful
-     *
-     */
-    public void setActionSuccessful() {
-//        oneActionWasSuccessful = true;
-    }
-
-    public Response checkWasSuccessfulBrowserAction() {
-        if (oneActionWasSuccessful && isBrowserFlow()) {
-            // redirect to non-action url so browser refresh button works without reposting past data
-            String code = generateCode();
-
-            URI redirect = LoginActionsService.loginActionsBaseUrl(getUriInfo())
-                    .path(flowPath)
-                    .queryParam(OAuth2Constants.CODE, code).build(getRealm().getName());
-            return Response.status(302).location(redirect).build();
-        } else {
-            return null;
-        }
-    }
-
     // May create userSession too
     public AuthenticatedClientSessionModel attachSession() {
         AuthenticatedClientSessionModel clientSession = attachSession(authenticationSession, userSession, session, realm, connection, event);
@@ -866,10 +854,28 @@ public class AuthenticationProcessor {
         if (attemptedUsername != null) username = attemptedUsername;
         String rememberMe = authSession.getAuthNote(Details.REMEMBER_ME);
         boolean remember = rememberMe != null && rememberMe.equalsIgnoreCase("true");
+
         if (userSession == null) { // if no authenticator attached a usersession
-            userSession = session.sessions().createUserSession(realm, authSession.getAuthenticatedUser(), username, connection.getRemoteAddr(), authSession.getProtocol(), remember, null, null);
+
+            userSession = session.sessions().getUserSession(realm, authSession.getId());
+            if (userSession == null) {
+                String brokerSessionId = authSession.getAuthNote(BROKER_SESSION_ID);
+                String brokerUserId = authSession.getAuthNote(BROKER_USER_ID);
+                userSession = session.sessions().createUserSession(authSession.getId(), realm, authSession.getAuthenticatedUser(), username, connection.getRemoteAddr(), authSession.getProtocol()
+                        , remember, brokerSessionId, brokerUserId);
+            } else {
+                // We have existing userSession even if it wasn't attached to authenticator. Could happen if SSO authentication was ignored (eg. prompt=login) and in some other cases.
+                // We need to handle case when different user was used and update that (TODO:mposolda evaluate this again and corner cases like token refresh etc. AND ROLES!!! LIKELY ERROR SHOULD BE SHOWN IF ATTEMPT TO AUTHENTICATE AS DIFFERENT USER)
+                logger.info("No SSO login, but found existing userSession with ID '%s' after finished authentication.");
+                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());
+                }
+            }
             userSession.setState(UserSessionModel.State.LOGGING_IN);
         }
+
         if (remember) {
             event.detail(Details.REMEMBER_ME, "true");
         }
@@ -909,24 +915,19 @@ public class AuthenticationProcessor {
         // attachSession(); // Session will be attached after requiredActions + consents are finished.
         AuthenticationManager.setRolesAndMappersInSession(authenticationSession);
 
-        if (isActionRequired()) {
-            // TODO:mposolda This was changed to avoid additional redirect. Doublecheck consequences...
-            //return redirectToRequiredActions(session, realm, authenticationSession, uriInfo);
-            ClientSessionCode<AuthenticationSessionModel> accessCode = new ClientSessionCode<>(session, realm, authenticationSession);
-            accessCode.setAction(ClientSessionModel.Action.REQUIRED_ACTIONS.name());
-            authenticationSession.setAuthNote(CURRENT_FLOW_PATH, LoginActionsService.REQUIRED_ACTION);
-
-            return AuthenticationManager.nextActionAfterAuthentication(session, authenticationSession, connection, request, uriInfo, event);
+        String nextRequiredAction = nextRequiredAction();
+        if (nextRequiredAction != null) {
+            return AuthenticationManager.redirectToRequiredActions(session, realm, authenticationSession, uriInfo, nextRequiredAction);
         } else {
             event.detail(Details.CODE_ID, authenticationSession.getId());  // todo This should be set elsewhere.  find out why tests fail.  Don't know where this is supposed to be set
             // the user has successfully logged in and we can clear his/her previous login failure attempts.
             logSuccess();
-            return AuthenticationManager.finishedRequiredActions(session, authenticationSession, connection, request, uriInfo, event);
+            return AuthenticationManager.finishedRequiredActions(session, authenticationSession, userSession, connection, request, uriInfo, event);
         }
     }
 
-    public boolean isActionRequired() {
-        return AuthenticationManager.isActionRequired(session, authenticationSession, connection, request, uriInfo, event);
+    public String nextRequiredAction() {
+        return AuthenticationManager.nextRequiredAction(session, authenticationSession, connection, request, uriInfo, event);
     }
 
     public AuthenticationProcessor.Result createAuthenticatorContext(AuthenticationExecutionModel model, Authenticator authenticator, List<AuthenticationExecutionModel> executions) {
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/broker/AbstractIdpAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/broker/AbstractIdpAuthenticator.java
index 9ba2053..fc7b941 100644
--- a/services/src/main/java/org/keycloak/authentication/authenticators/broker/AbstractIdpAuthenticator.java
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/broker/AbstractIdpAuthenticator.java
@@ -48,7 +48,7 @@ public abstract class AbstractIdpAuthenticator implements Authenticator {
     public static final String UPDATE_PROFILE_EMAIL_CHANGED = "UPDATE_PROFILE_EMAIL_CHANGED";
 
     // The clientSession note flag to indicate if re-authentication after first broker login happened in different browser window. This can happen for example during email verification
-    public static final String IS_DIFFERENT_BROWSER = "IS_DIFFERENT_BROWSER";
+    public static final String IS_DIFFERENT_BROWSER = "IS_DIFFERENT_BROWSER"; // TODO:mposolda can reuse the END_AFTER_REQUIRED_ACTIONS instead?
 
     // The clientSession note flag to indicate that updateProfile page will be always displayed even if "updateProfileOnFirstLogin" is off
     public static final String ENFORCE_UPDATE_PROFILE = "ENFORCE_UPDATE_PROFILE";
@@ -56,12 +56,15 @@ public abstract class AbstractIdpAuthenticator implements Authenticator {
     // clientSession.note flag specifies if we imported new user to keycloak (true) or we just linked to an existing keycloak user (false)
     public static final String BROKER_REGISTERED_NEW_USER = "BROKER_REGISTERED_NEW_USER";
 
+    // Set after firstBrokerLogin is successfully finished and contains the providerId of the provider, whose 'first-broker-login' flow was just finished
+    public static final String FIRST_BROKER_LOGIN_SUCCESS = "FIRST_BROKER_LOGIN_SUCCESS";
+
 
     @Override
     public void authenticate(AuthenticationFlowContext context) {
         AuthenticationSessionModel authSession = context.getAuthenticationSession();
 
-        SerializedBrokeredIdentityContext serializedCtx = SerializedBrokeredIdentityContext.readFromLoginSession(authSession, BROKERED_CONTEXT_NOTE);
+        SerializedBrokeredIdentityContext serializedCtx = SerializedBrokeredIdentityContext.readFromAuthenticationSession(authSession, BROKERED_CONTEXT_NOTE);
         if (serializedCtx == null) {
             throw new AuthenticationFlowException("Not found serialized context in clientSession", AuthenticationFlowError.IDENTITY_PROVIDER_ERROR);
         }
@@ -78,7 +81,7 @@ public abstract class AbstractIdpAuthenticator implements Authenticator {
     public void action(AuthenticationFlowContext context) {
         AuthenticationSessionModel clientSession = context.getAuthenticationSession();
 
-        SerializedBrokeredIdentityContext serializedCtx = SerializedBrokeredIdentityContext.readFromLoginSession(clientSession, BROKERED_CONTEXT_NOTE);
+        SerializedBrokeredIdentityContext serializedCtx = SerializedBrokeredIdentityContext.readFromAuthenticationSession(clientSession, BROKERED_CONTEXT_NOTE);
         if (serializedCtx == null) {
             throw new AuthenticationFlowException("Not found serialized context in clientSession", AuthenticationFlowError.IDENTITY_PROVIDER_ERROR);
         }
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpConfirmLinkAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpConfirmLinkAuthenticator.java
index d4cfcf5..3ed3dd7 100755
--- a/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpConfirmLinkAuthenticator.java
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpConfirmLinkAuthenticator.java
@@ -20,6 +20,7 @@ package org.keycloak.authentication.authenticators.broker;
 import org.keycloak.authentication.AuthenticationFlowContext;
 import org.keycloak.authentication.AuthenticationFlowError;
 import org.keycloak.authentication.AuthenticationFlowException;
+import org.keycloak.authentication.AuthenticationProcessor;
 import org.keycloak.authentication.authenticators.broker.util.ExistingUserInfo;
 import org.keycloak.authentication.authenticators.broker.util.SerializedBrokeredIdentityContext;
 import org.keycloak.broker.provider.BrokeredIdentityContext;
@@ -65,9 +66,12 @@ public class IdpConfirmLinkAuthenticator extends AbstractIdpAuthenticator {
 
         String action = formData.getFirst("submitAction");
         if (action != null && action.equals("updateProfile")) {
-            context.getAuthenticationSession().setAuthNote(ENFORCE_UPDATE_PROFILE, "true");
-            context.getAuthenticationSession().removeAuthNote(EXISTING_USER_INFO);
-            context.resetFlow();
+            context.resetFlow(() -> {
+                AuthenticationSessionModel authSession = context.getAuthenticationSession();
+
+                serializedCtx.saveToAuthenticationSession(authSession, BROKERED_CONTEXT_NOTE);
+                authSession.setAuthNote(ENFORCE_UPDATE_PROFILE, "true");
+            });
         } else if (action != null && action.equals("linkAccount")) {
             context.success();
         } else {
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpEmailVerificationAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpEmailVerificationAuthenticator.java
index 27a30e8..72064a0 100755
--- a/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpEmailVerificationAuthenticator.java
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpEmailVerificationAuthenticator.java
@@ -38,6 +38,7 @@ import org.keycloak.models.UserModel;
 import org.keycloak.services.ServicesLogger;
 import org.keycloak.services.messages.Messages;
 import org.keycloak.services.resources.LoginActionsService;
+import org.keycloak.sessions.AuthenticationSessionModel;
 
 import javax.ws.rs.core.MultivaluedMap;
 import javax.ws.rs.core.Response;
@@ -53,16 +54,17 @@ public class IdpEmailVerificationAuthenticator extends AbstractIdpAuthenticator 
 
     @Override
     protected void authenticateImpl(AuthenticationFlowContext context, SerializedBrokeredIdentityContext serializedCtx, BrokeredIdentityContext brokerContext) {
-        /*KeycloakSession session = context.getSession();
+        KeycloakSession session = context.getSession();
         RealmModel realm = context.getRealm();
-        ClientSessionModel clientSession = context.getClientSession();
+        AuthenticationSessionModel authSession = context.getAuthenticationSession();
 
-        if (realm.getSmtpConfig().size() == 0) {
+        // TODO:mposolda (or hmlnarik :) - uncomment and have this working and have AbstractFirstBrokerLoginTest.testLinkAccountByEmailVerification tp PASS
+//        if (realm.getSmtpConfig().size() == 0) {
             ServicesLogger.LOGGER.smtpNotConfigured();
             context.attempted();
             return;
-        }
-
+//        }
+/*
         VerifyEmail.setupKey(clientSession);
 
         UserModel existingUser = getExistingUser(session, realm, clientSession);
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpReviewProfileAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpReviewProfileAuthenticator.java
index 245f258..c430fef 100755
--- a/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpReviewProfileAuthenticator.java
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpReviewProfileAuthenticator.java
@@ -127,7 +127,7 @@ public class IdpReviewProfileAuthenticator extends AbstractIdpAuthenticator {
 
         AttributeFormDataProcessor.process(formData, realm, userCtx);
 
-        userCtx.saveToLoginSession(context.getAuthenticationSession(), BROKERED_CONTEXT_NOTE);
+        userCtx.saveToAuthenticationSession(context.getAuthenticationSession(), BROKERED_CONTEXT_NOTE);
 
         logger.debugf("Profile updated successfully after first authentication with identity provider '%s' for broker user '%s'.", brokerContext.getIdpConfig().getAlias(), userCtx.getUsername());
 
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpUsernamePasswordForm.java b/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpUsernamePasswordForm.java
index edcc030..0ea8157 100755
--- a/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpUsernamePasswordForm.java
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpUsernamePasswordForm.java
@@ -58,7 +58,7 @@ public class IdpUsernamePasswordForm extends UsernamePasswordForm {
     }
 
     protected LoginFormsProvider setupForm(AuthenticationFlowContext context, MultivaluedMap<String, String> formData, UserModel existingUser) {
-        SerializedBrokeredIdentityContext serializedCtx = SerializedBrokeredIdentityContext.readFromLoginSession(context.getAuthenticationSession(), AbstractIdpAuthenticator.BROKERED_CONTEXT_NOTE);
+        SerializedBrokeredIdentityContext serializedCtx = SerializedBrokeredIdentityContext.readFromAuthenticationSession(context.getAuthenticationSession(), AbstractIdpAuthenticator.BROKERED_CONTEXT_NOTE);
         if (serializedCtx == null) {
             throw new AuthenticationFlowException("Not found serialized context in clientSession", AuthenticationFlowError.IDENTITY_PROVIDER_ERROR);
         }
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/broker/util/SerializedBrokeredIdentityContext.java b/services/src/main/java/org/keycloak/authentication/authenticators/broker/util/SerializedBrokeredIdentityContext.java
index e648242..a9c6d1e 100755
--- a/services/src/main/java/org/keycloak/authentication/authenticators/broker/util/SerializedBrokeredIdentityContext.java
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/broker/util/SerializedBrokeredIdentityContext.java
@@ -313,8 +313,8 @@ public class SerializedBrokeredIdentityContext implements UpdateProfileContext {
         return ctx;
     }
 
-    // Save this context as note to clientSession
-    public void saveToLoginSession(AuthenticationSessionModel authSession, String noteKey) {
+    // Save this context as note to authSession
+    public void saveToAuthenticationSession(AuthenticationSessionModel authSession, String noteKey) {
         try {
             String asString = JsonSerialization.writeValueAsString(this);
             authSession.setAuthNote(noteKey, asString);
@@ -323,7 +323,7 @@ public class SerializedBrokeredIdentityContext implements UpdateProfileContext {
         }
     }
 
-    public static SerializedBrokeredIdentityContext readFromLoginSession(AuthenticationSessionModel authSession, String noteKey) {
+    public static SerializedBrokeredIdentityContext readFromAuthenticationSession(AuthenticationSessionModel authSession, String noteKey) {
         String asString = authSession.getAuthNote(noteKey);
         if (asString == null) {
             return null;
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/browser/CookieAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/browser/CookieAuthenticator.java
index 73c92cf..cf7e1a0 100755
--- a/services/src/main/java/org/keycloak/authentication/authenticators/browser/CookieAuthenticator.java
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/browser/CookieAuthenticator.java
@@ -51,7 +51,7 @@ public class CookieAuthenticator implements Authenticator {
             if (protocol.requireReauthentication(authResult.getSession(), clientSession)) {
                 context.attempted();
             } else {
-                clientSession.setNote(AuthenticationManager.SSO_AUTH, "true");
+                clientSession.setClientNote(AuthenticationManager.SSO_AUTH, "true");
 
                 context.setUser(authResult.getUser());
                 context.attachUserSession(authResult.getSession());
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/browser/UsernamePasswordForm.java b/services/src/main/java/org/keycloak/authentication/authenticators/browser/UsernamePasswordForm.java
index eaa95bb..bd81263 100755
--- a/services/src/main/java/org/keycloak/authentication/authenticators/browser/UsernamePasswordForm.java
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/browser/UsernamePasswordForm.java
@@ -59,7 +59,7 @@ public class UsernamePasswordForm extends AbstractUsernameFormAuthenticator impl
     @Override
     public void authenticate(AuthenticationFlowContext context) {
         MultivaluedMap<String, String> formData = new MultivaluedMapImpl<>();
-        String loginHint = context.getAuthenticationSession().getNote(OIDCLoginProtocol.LOGIN_HINT_PARAM);
+        String loginHint = context.getAuthenticationSession().getClientNote(OIDCLoginProtocol.LOGIN_HINT_PARAM);
 
         String rememberMeUsername = AuthenticationManager.getRememberMeUsername(context.getRealm(), context.getHttpRequest().getHttpHeaders());
 
@@ -72,7 +72,6 @@ public class UsernamePasswordForm extends AbstractUsernameFormAuthenticator impl
             }
         }
         Response challengeResponse = challenge(context, formData);
-        context.getAuthenticationSession().setAuthNote(AuthenticationProcessor.CURRENT_AUTHENTICATION_EXECUTION, context.getExecution().getId());
         context.challenge(challengeResponse);
     }
 
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/x509/ValidateX509CertificateUsername.java b/services/src/main/java/org/keycloak/authentication/authenticators/x509/ValidateX509CertificateUsername.java
index 46f800e..89048ac 100644
--- a/services/src/main/java/org/keycloak/authentication/authenticators/x509/ValidateX509CertificateUsername.java
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/x509/ValidateX509CertificateUsername.java
@@ -92,7 +92,7 @@ public class ValidateX509CertificateUsername extends AbstractX509ClientCertifica
         UserModel user;
         try {
             context.getEvent().detail(Details.USERNAME, userIdentity.toString());
-            context.getAuthenticationSession().setNote(AbstractUsernameFormAuthenticator.ATTEMPTED_USERNAME, userIdentity.toString());
+            context.getAuthenticationSession().setAuthNote(AbstractUsernameFormAuthenticator.ATTEMPTED_USERNAME, userIdentity.toString());
             user = getUserIdentityToModelMapper(config).find(context, userIdentity);
         }
         catch(ModelDuplicateException e) {
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/x509/X509ClientCertificateAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/x509/X509ClientCertificateAuthenticator.java
index 01345ba..2aa5a63 100644
--- a/services/src/main/java/org/keycloak/authentication/authenticators/x509/X509ClientCertificateAuthenticator.java
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/x509/X509ClientCertificateAuthenticator.java
@@ -166,7 +166,6 @@ public class X509ClientCertificateAuthenticator extends AbstractX509ClientCertif
                 // to call the method "challenge" results in a wrong/unexpected behavior.
                 // The question is whether calling "forceChallenge" here is ok from
                 // the design viewpoint?
-                context.getAuthenticationSession().setAuthNote(AuthenticationProcessor.CURRENT_AUTHENTICATION_EXECUTION, context.getExecution().getId());
                 context.forceChallenge(createSuccessResponse(context, certs[0].getSubjectDN().getName()));
                 // Do not set the flow status yet, we want to display a form to let users
                 // choose whether to accept the identity from certificate or to specify username/password explicitly
diff --git a/services/src/main/java/org/keycloak/authentication/DefaultAuthenticationFlow.java b/services/src/main/java/org/keycloak/authentication/DefaultAuthenticationFlow.java
index c8ec6b2..dfa1afa 100755
--- a/services/src/main/java/org/keycloak/authentication/DefaultAuthenticationFlow.java
+++ b/services/src/main/java/org/keycloak/authentication/DefaultAuthenticationFlow.java
@@ -93,10 +93,6 @@ public class DefaultAuthenticationFlow implements AuthenticationFlow {
                 Response response = processResult(result, true);
                 if (response == null) {
                     processor.getAuthenticationSession().removeAuthNote(AuthenticationProcessor.CURRENT_AUTHENTICATION_EXECUTION);
-                    if (result.status == FlowStatus.SUCCESS) {
-                        // we do this so that flow can redirect to a non-action URL
-                        processor.setActionSuccessful();
-                    }
                     return processFlow();
                 } else return response;
             }
@@ -183,8 +179,8 @@ public class DefaultAuthenticationFlow implements AuthenticationFlow {
                 }
             }
             // skip if action as successful already
-            Response redirect = processor.checkWasSuccessfulBrowserAction();
-            if (redirect != null) return redirect;
+//            Response redirect = processor.checkWasSuccessfulBrowserAction();
+//            if (redirect != null) return redirect;
 
             AuthenticationProcessor.Result context = processor.createAuthenticatorContext(model, authenticator, executions);
             logger.debug("invoke authenticator.authenticate");
@@ -203,13 +199,6 @@ public class DefaultAuthenticationFlow implements AuthenticationFlow {
             case SUCCESS:
                 logger.debugv("authenticator SUCCESS: {0}", execution.getAuthenticator());
                 processor.getAuthenticationSession().setExecutionStatus(execution.getId(), ClientSessionModel.ExecutionStatus.SUCCESS);
-
-                // We just do another GET to ensure that page refresh will work
-                if (isAction) {
-                    processor.getAuthenticationSession().removeAuthNote(AuthenticationProcessor.CURRENT_AUTHENTICATION_EXECUTION);
-                    return processor.redirectToFlow(execution.getId());
-                }
-
                 if (execution.isAlternative()) alternativeSuccessful = true;
                 return null;
             case FAILED:
@@ -258,7 +247,7 @@ public class DefaultAuthenticationFlow implements AuthenticationFlow {
                 processor.getAuthenticationSession().setExecutionStatus(execution.getId(), ClientSessionModel.ExecutionStatus.ATTEMPTED);
                 return null;
             case FLOW_RESET:
-                AuthenticationProcessor.resetFlow(processor.getAuthenticationSession());
+                processor.resetFlow();
                 return processor.authenticate();
             default:
                 logger.debugv("authenticator INTERNAL_ERROR: {0}", execution.getAuthenticator());
diff --git a/services/src/main/java/org/keycloak/authentication/FormAuthenticationFlow.java b/services/src/main/java/org/keycloak/authentication/FormAuthenticationFlow.java
index 17898f4..0e121dd 100755
--- a/services/src/main/java/org/keycloak/authentication/FormAuthenticationFlow.java
+++ b/services/src/main/java/org/keycloak/authentication/FormAuthenticationFlow.java
@@ -244,7 +244,6 @@ public class FormAuthenticationFlow implements AuthenticationFlow {
         }
         processor.getAuthenticationSession().setExecutionStatus(actionExecution, ClientSessionModel.ExecutionStatus.SUCCESS);
         processor.getAuthenticationSession().removeAuthNote(AuthenticationProcessor.CURRENT_AUTHENTICATION_EXECUTION);
-        processor.setActionSuccessful();
         return null;
     }
 
diff --git a/services/src/main/java/org/keycloak/authentication/forms/RegistrationUserCreation.java b/services/src/main/java/org/keycloak/authentication/forms/RegistrationUserCreation.java
index ad13212..6567aef 100755
--- a/services/src/main/java/org/keycloak/authentication/forms/RegistrationUserCreation.java
+++ b/services/src/main/java/org/keycloak/authentication/forms/RegistrationUserCreation.java
@@ -134,7 +134,7 @@ public class RegistrationUserCreation implements FormAction, FormActionFactory {
         user.setEnabled(true);
 
         user.setEmail(email);
-        context.getAuthenticationSession().setNote(OIDCLoginProtocol.LOGIN_HINT_PARAM, username);
+        context.getAuthenticationSession().setClientNote(OIDCLoginProtocol.LOGIN_HINT_PARAM, username);
         AttributeFormDataProcessor.process(formData, context.getRealm(), user);
         context.setUser(user);
         context.getEvent().user(user);
diff --git a/services/src/main/java/org/keycloak/authorization/admin/PolicyEvaluationService.java b/services/src/main/java/org/keycloak/authorization/admin/PolicyEvaluationService.java
index 8a7ea61..82cbddf 100644
--- a/services/src/main/java/org/keycloak/authorization/admin/PolicyEvaluationService.java
+++ b/services/src/main/java/org/keycloak/authorization/admin/PolicyEvaluationService.java
@@ -62,10 +62,14 @@ import org.keycloak.models.RoleModel;
 import org.keycloak.models.UserModel;
 import org.keycloak.models.UserSessionModel;
 import org.keycloak.protocol.oidc.TokenManager;
+import org.keycloak.models.utils.KeycloakModelUtils;
+import org.keycloak.protocol.ProtocolMapper;
+import org.keycloak.protocol.oidc.mappers.OIDCAccessTokenMapper;
 import org.keycloak.representations.AccessToken;
 import org.keycloak.representations.idm.authorization.ResourceRepresentation;
 import org.keycloak.representations.idm.authorization.ScopeRepresentation;
 import org.keycloak.services.Urls;
+import org.keycloak.services.managers.AuthenticationManager;
 import org.keycloak.services.resources.admin.RealmAuth;
 import org.keycloak.sessions.AuthenticationSessionModel;
 
@@ -229,11 +233,14 @@ public class PolicyEvaluationService {
 
                 if (clientId != null) {
                     ClientModel clientModel = realm.getClientById(clientId);
-                    AuthenticationSessionModel authSession = keycloakSession.authenticationSessions().createAuthenticationSession(realm, clientModel, false);
+                    String id = KeycloakModelUtils.generateId();
+
+                    AuthenticationSessionModel authSession = keycloakSession.authenticationSessions().createAuthenticationSession(id, realm, clientModel);
                     authSession.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL);
-                    userSession = keycloakSession.sessions().createUserSession(realm, userModel, userModel.getUsername(), "127.0.0.1", "passwd", false, null, null);
+                    userSession = keycloakSession.sessions().createUserSession(id, realm, userModel, userModel.getUsername(), "127.0.0.1", "passwd", false, null, null);
 
-                    clientSession = new TokenManager().attachAuthenticationSession(keycloakSession, userSession, authSession);
+                    AuthenticationManager.setRolesAndMappersInSession(authSession);
+                    clientSession = TokenManager.attachAuthenticationSession(keycloakSession, userSession, authSession);
 
                     Set<RoleModel> requestedRoles = new HashSet<>();
                     for (String roleId : clientSession.getRoles()) {
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 7f02e43..339747a 100755
--- a/services/src/main/java/org/keycloak/broker/oidc/AbstractOAuth2IdentityProvider.java
+++ b/services/src/main/java/org/keycloak/broker/oidc/AbstractOAuth2IdentityProvider.java
@@ -37,6 +37,7 @@ import org.keycloak.services.messages.Messages;
 
 import javax.ws.rs.GET;
 import javax.ws.rs.QueryParam;
+import javax.ws.rs.WebApplicationException;
 import javax.ws.rs.core.Context;
 import javax.ws.rs.core.HttpHeaders;
 import javax.ws.rs.core.Response;
@@ -240,6 +241,8 @@ public abstract class AbstractOAuth2IdentityProvider<C extends OAuth2IdentityPro
 
                     return callback.authenticated(federatedIdentity);
                 }
+            } catch (WebApplicationException e) {
+                return e.getResponse();
             } catch (Exception e) {
                 logger.error("Failed to make identity provider oauth callback", e);
             }
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 4a8a71a..4f24373 100755
--- a/services/src/main/java/org/keycloak/broker/oidc/OIDCIdentityProvider.java
+++ b/services/src/main/java/org/keycloak/broker/oidc/OIDCIdentityProvider.java
@@ -44,6 +44,7 @@ import org.keycloak.services.managers.AuthenticationManager;
 import org.keycloak.services.messages.Messages;
 import org.keycloak.services.resources.IdentityBrokerService;
 import org.keycloak.services.resources.RealmsResource;
+import org.keycloak.sessions.AuthenticationSessionModel;
 import org.keycloak.util.JsonSerialization;
 
 import javax.ws.rs.GET;
@@ -350,14 +351,14 @@ public class OIDCIdentityProvider extends AbstractOAuth2IdentityProvider<OIDCIde
     }
 
     @Override
-    public void attachUserSession(UserSessionModel userSession, ClientSessionModel clientSession, BrokeredIdentityContext context) {
+    public void authenticationFinished(AuthenticationSessionModel authSession, BrokeredIdentityContext context)  {
         AccessTokenResponse tokenResponse = (AccessTokenResponse)context.getContextData().get(FEDERATED_ACCESS_TOKEN_RESPONSE);
         int currentTime = Time.currentTime();
         long expiration = tokenResponse.getExpiresIn() > 0 ? tokenResponse.getExpiresIn() + currentTime : 0;
-        userSession.setNote(FEDERATED_TOKEN_EXPIRATION, Long.toString(expiration));
-        userSession.setNote(FEDERATED_REFRESH_TOKEN, tokenResponse.getRefreshToken());
-        userSession.setNote(FEDERATED_ACCESS_TOKEN, tokenResponse.getToken());
-        userSession.setNote(FEDERATED_ID_TOKEN, tokenResponse.getIdToken());
+        authSession.setUserSessionNote(FEDERATED_TOKEN_EXPIRATION, Long.toString(expiration));
+        authSession.setUserSessionNote(FEDERATED_REFRESH_TOKEN, tokenResponse.getRefreshToken());
+        authSession.setUserSessionNote(FEDERATED_ACCESS_TOKEN, tokenResponse.getToken());
+        authSession.setUserSessionNote(FEDERATED_ID_TOKEN, tokenResponse.getIdToken());
     }
 
     @Override
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 5825f60..dc38d59 100755
--- a/services/src/main/java/org/keycloak/broker/saml/SAMLEndpoint.java
+++ b/services/src/main/java/org/keycloak/broker/saml/SAMLEndpoint.java
@@ -71,6 +71,7 @@ import javax.ws.rs.POST;
 import javax.ws.rs.Path;
 import javax.ws.rs.QueryParam;
 import javax.ws.rs.PathParam;
+import javax.ws.rs.WebApplicationException;
 import javax.ws.rs.core.Context;
 import javax.ws.rs.core.HttpHeaders;
 import javax.ws.rs.core.MediaType;
@@ -436,7 +437,8 @@ public class SAMLEndpoint {
 
 
                 return callback.authenticated(identity);
-
+            } catch (WebApplicationException e) {
+                return e.getResponse();
             } catch (Exception e) {
                 throw new IdentityBrokerException("Could not process response from SAML identity provider.", e);
             }
diff --git a/services/src/main/java/org/keycloak/broker/saml/SAMLIdentityProvider.java b/services/src/main/java/org/keycloak/broker/saml/SAMLIdentityProvider.java
index c28cda8..5f02b8e 100755
--- a/services/src/main/java/org/keycloak/broker/saml/SAMLIdentityProvider.java
+++ b/services/src/main/java/org/keycloak/broker/saml/SAMLIdentityProvider.java
@@ -56,6 +56,7 @@ import java.util.TreeSet;
 import org.keycloak.dom.saml.v2.metadata.KeyTypes;
 import org.keycloak.keys.KeyMetadata;
 import org.keycloak.saml.processing.core.util.KeycloakKeySamlExtensionGenerator;
+import org.keycloak.sessions.AuthenticationSessionModel;
 
 /**
  * @author Pedro Igor
@@ -132,17 +133,17 @@ public class SAMLIdentityProvider extends AbstractIdentityProvider<SAMLIdentityP
     }
 
     @Override
-    public void attachUserSession(UserSessionModel userSession, ClientSessionModel clientSession, BrokeredIdentityContext context) {
+    public void authenticationFinished(AuthenticationSessionModel authSession, BrokeredIdentityContext context)  {
         ResponseType responseType = (ResponseType)context.getContextData().get(SAMLEndpoint.SAML_LOGIN_RESPONSE);
         AssertionType assertion = (AssertionType)context.getContextData().get(SAMLEndpoint.SAML_ASSERTION);
         SubjectType subject = assertion.getSubject();
         SubjectType.STSubType subType = subject.getSubType();
         NameIDType subjectNameID = (NameIDType) subType.getBaseID();
-        userSession.setNote(SAMLEndpoint.SAML_FEDERATED_SUBJECT, subjectNameID.getValue());
-        if (subjectNameID.getFormat() != null) userSession.setNote(SAMLEndpoint.SAML_FEDERATED_SUBJECT_NAMEFORMAT, subjectNameID.getFormat().toString());
+        authSession.setUserSessionNote(SAMLEndpoint.SAML_FEDERATED_SUBJECT, subjectNameID.getValue());
+        if (subjectNameID.getFormat() != null) authSession.setUserSessionNote(SAMLEndpoint.SAML_FEDERATED_SUBJECT_NAMEFORMAT, subjectNameID.getFormat().toString());
         AuthnStatementType authn =  (AuthnStatementType)context.getContextData().get(SAMLEndpoint.SAML_AUTHN_STATEMENT);
         if (authn != null && authn.getSessionIndex() != null) {
-            userSession.setNote(SAMLEndpoint.SAML_FEDERATED_SESSION_INDEX, authn.getSessionIndex());
+            authSession.setUserSessionNote(SAMLEndpoint.SAML_FEDERATED_SESSION_INDEX, authn.getSessionIndex());
 
         }
     }
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 b7e6679..ffb3f4d 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
@@ -179,6 +179,17 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
 
         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);
+        }
+        URI baseUri = uriBuilder.build();
+
+        if (accessCode != null) {
+            uriBuilder.queryParam(OAuth2Constants.CODE, accessCode);
+        }
+        URI baseUriWithCode = uriBuilder.build();
+
 
         for (String k : queryParameterMap.keySet()) {
 
@@ -228,11 +239,6 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
         }
         attributes.put("messagesPerField", messagesPerField);
 
-        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);
-        }
-        URI baseUri = uriBuilder.build();
         attributes.put("requiredActionUrl", new RequiredActionUrlFormatterMethod(realm, baseUri));
         if (realm != null && user != null && session != null) {
             attributes.put("authenticatorConfigured", new AuthenticatorConfiguredMethod(realm, user, session));
@@ -243,7 +249,7 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
 
             List<IdentityProviderModel> identityProviders = realm.getIdentityProviders();
             identityProviders = LoginFormsUtil.filterIdentityProviders(identityProviders, session, realm, attributes, formData);
-            attributes.put("social", new IdentityProviderBean(realm, session, identityProviders, baseUri, uriInfo));
+            attributes.put("social", new IdentityProviderBean(realm, session, identityProviders, baseUriWithCode));
 
             attributes.put("url", new UrlBean(realm, theme, baseUri, this.actionUri));
 
@@ -340,6 +346,11 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
         }
         URI baseUri = uriBuilder.build();
 
+        if (accessCode != null) {
+            uriBuilder.queryParam(OAuth2Constants.CODE, accessCode);
+        }
+        URI baseUriWithCode = uriBuilder.build();
+
         ThemeProvider themeProvider = session.getProvider(ThemeProvider.class, "extending");
         Theme theme;
         try {
@@ -391,7 +402,7 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
 
             List<IdentityProviderModel> identityProviders = realm.getIdentityProviders();
             identityProviders = LoginFormsUtil.filterIdentityProviders(identityProviders, session, realm, attributes, formData);
-            attributes.put("social", new IdentityProviderBean(realm, session, identityProviders, baseUri, uriInfo));
+            attributes.put("social", new IdentityProviderBean(realm, session, identityProviders, baseUriWithCode));
 
             attributes.put("url", new UrlBean(realm, theme, baseUri, this.actionUri));
             attributes.put("requiredActionUrl", new RequiredActionUrlFormatterMethod(realm, baseUri));
diff --git a/services/src/main/java/org/keycloak/forms/login/freemarker/model/IdentityProviderBean.java b/services/src/main/java/org/keycloak/forms/login/freemarker/model/IdentityProviderBean.java
index 696e19b..986d0ef 100755
--- a/services/src/main/java/org/keycloak/forms/login/freemarker/model/IdentityProviderBean.java
+++ b/services/src/main/java/org/keycloak/forms/login/freemarker/model/IdentityProviderBean.java
@@ -42,7 +42,7 @@ public class IdentityProviderBean {
     private RealmModel realm;
     private final KeycloakSession session;
 
-    public IdentityProviderBean(RealmModel realm, KeycloakSession session, List<IdentityProviderModel> identityProviders, URI baseURI, UriInfo uriInfo) {
+    public IdentityProviderBean(RealmModel realm, KeycloakSession session, List<IdentityProviderModel> identityProviders, URI baseURI) {
         this.realm = realm;
         this.session = session;
 
diff --git a/services/src/main/java/org/keycloak/protocol/AuthorizationEndpointBase.java b/services/src/main/java/org/keycloak/protocol/AuthorizationEndpointBase.java
index e81e9e5..89680bb 100755
--- a/services/src/main/java/org/keycloak/protocol/AuthorizationEndpointBase.java
+++ b/services/src/main/java/org/keycloak/protocol/AuthorizationEndpointBase.java
@@ -17,17 +17,25 @@
 
 package org.keycloak.protocol;
 
+import org.jboss.logging.Logger;
 import org.jboss.resteasy.spi.HttpRequest;
 import org.keycloak.authentication.AuthenticationProcessor;
 import org.keycloak.common.ClientConnection;
+import org.keycloak.common.util.ObjectUtil;
 import org.keycloak.events.Details;
 import org.keycloak.events.EventBuilder;
 import org.keycloak.models.AuthenticationFlowModel;
+import org.keycloak.models.ClientModel;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserSessionModel;
 import org.keycloak.protocol.LoginProtocol.Error;
 import org.keycloak.services.managers.AuthenticationManager;
+import org.keycloak.services.managers.AuthenticationSessionManager;
+import org.keycloak.services.managers.ClientSessionCode;
 import org.keycloak.services.resources.LoginActionsService;
+import org.keycloak.services.util.CacheControlUtil;
+import org.keycloak.services.util.PageExpiredRedirect;
 import org.keycloak.sessions.AuthenticationSessionModel;
 
 import javax.ws.rs.core.Context;
@@ -42,6 +50,10 @@ import javax.ws.rs.core.UriInfo;
  */
 public abstract class AuthorizationEndpointBase {
 
+    private static final Logger logger = Logger.getLogger(AuthorizationEndpointBase.class);
+
+    public static final String APP_INITIATED_FLOW = "APP_INITIATED_FLOW";
+
     protected RealmModel realm;
     protected EventBuilder event;
     protected AuthenticationManager authManager;
@@ -103,15 +115,13 @@ public abstract class AuthorizationEndpointBase {
                     // processor.attachSession();
                 } else {
                     Response response = protocol.sendError(authSession, Error.PASSIVE_LOGIN_REQUIRED);
-                    session.authenticationSessions().removeAuthenticationSession(realm, authSession);
                     return response;
                 }
 
                 AuthenticationManager.setRolesAndMappersInSession(authSession);
 
-                if (processor.isActionRequired()) {
+                if (processor.nextRequiredAction() != null) {
                     Response response = protocol.sendError(authSession, Error.PASSIVE_INTERACTION_REQUIRED);
-                    session.authenticationSessions().removeAuthenticationSession(realm, authSession);
                     return response;
                 }
 
@@ -125,7 +135,7 @@ public abstract class AuthorizationEndpointBase {
             try {
                 RestartLoginCookie.setRestartCookie(session, realm, clientConnection, uriInfo, authSession);
                 if (redirectToAuthentication) {
-                    return processor.redirectToFlow(null);
+                    return processor.redirectToFlow();
                 }
                 return processor.authenticate();
             } catch (Exception e) {
@@ -138,4 +148,115 @@ public abstract class AuthorizationEndpointBase {
         return realm.getBrowserFlow();
     }
 
+
+    protected AuthorizationEndpointChecks getOrCreateAuthenticationSession(ClientModel client, String requestState) {
+        AuthenticationSessionManager manager = new AuthenticationSessionManager(session);
+        String authSessionId = manager.getCurrentAuthenticationSessionId(realm);
+        AuthenticationSessionModel authSession = authSessionId==null ? null : session.authenticationSessions().getAuthenticationSession(realm, authSessionId);
+
+        if (authSession != null) {
+
+            ClientSessionCode<AuthenticationSessionModel> check = new ClientSessionCode<>(session, realm, authSession);
+            if (!check.isActionActive(ClientSessionCode.ActionType.LOGIN)) {
+
+                logger.infof("Authentication session '%s' exists, but is expired. Restart existing authentication session", authSession.getId());
+                authSession.restartSession(realm, client);
+                return new AuthorizationEndpointChecks(authSession);
+
+            } else if (isNewRequest(authSession, client, requestState)) {
+                // Check if we have lastProcessedExecution and restart the session just if yes. Otherwise update just client information from the AuthorizationEndpoint request.
+                // This difference is needed, because of logout from JS applications in multiple browser tabs.
+                if (hasProcessedExecution(authSession)) {
+                    logger.info("New request from application received, but authentication session already exists. Restart existing authentication session");
+                    authSession.restartSession(realm, client);
+                } else {
+                    logger.info("New request from application received, but authentication session already exists. Update client information in existing authentication session");
+                    authSession.clearClientNotes(); // update client data
+                    authSession.updateClient(client);
+                }
+
+                return new AuthorizationEndpointChecks(authSession);
+
+            } else {
+                logger.info("Re-sent some previous request to Authorization endpoint. Likely browser 'back' or 'refresh' button.");
+
+                // See if we have lastProcessedExecution note. If yes, we are expired. Also if we are in different flow than initial one. Otherwise it is browser refresh of initial username/password form
+                if (!shouldShowExpirePage(authSession)) {
+                    return new AuthorizationEndpointChecks(authSession);
+                } else {
+                    CacheControlUtil.noBackButtonCacheControlHeader();
+
+                    Response response = new PageExpiredRedirect(session, realm, uriInfo)
+                            .showPageExpired(authSession);
+                    return new AuthorizationEndpointChecks(response);
+                }
+            }
+        }
+
+        UserSessionModel userSession = authSessionId==null ? null : session.sessions().getUserSession(realm, authSessionId);
+
+        if (userSession != null) {
+            logger.infof("Sent request to authz endpoint. We don't have authentication session with ID '%s' but we have userSession. Will re-create authentication session with same ID", authSessionId);
+            authSession = session.authenticationSessions().createAuthenticationSession(authSessionId, realm, client);
+        } else {
+            authSession = manager.createAuthenticationSession(realm, client, true);
+            logger.infof("Sent request to authz endpoint. Created new authentication session with ID '%s'", authSession.getId());
+        }
+
+        return new AuthorizationEndpointChecks(authSession);
+
+    }
+
+    private boolean hasProcessedExecution(AuthenticationSessionModel authSession) {
+        String lastProcessedExecution = authSession.getAuthNote(AuthenticationProcessor.LAST_PROCESSED_EXECUTION);
+        return (lastProcessedExecution != null);
+    }
+
+    // See if we have lastProcessedExecution note. If yes, we are expired. Also if we are in different flow than initial one. Otherwise it is browser refresh of initial username/password form
+    private boolean shouldShowExpirePage(AuthenticationSessionModel authSession) {
+        if (hasProcessedExecution(authSession)) {
+            return true;
+        }
+
+        String initialFlow = authSession.getClientNote(APP_INITIATED_FLOW);
+        if (initialFlow == null) {
+            initialFlow = LoginActionsService.AUTHENTICATE_PATH;
+        }
+
+        String lastFlow = authSession.getAuthNote(AuthenticationProcessor.CURRENT_FLOW_PATH);
+        // Check if we transitted between flows (eg. clicking "register" on login screen)
+        if (!initialFlow.equals(lastFlow)) {
+            logger.infof("Transition between flows! Current flow: %s, Previous flow: %s", initialFlow, lastFlow);
+
+            if (lastFlow == null || LoginActionsService.isFlowTransitionAllowed(initialFlow, lastFlow)) {
+                authSession.setAuthNote(AuthenticationProcessor.CURRENT_FLOW_PATH, initialFlow);
+                authSession.removeAuthNote(AuthenticationProcessor.CURRENT_AUTHENTICATION_EXECUTION);
+                return false;
+            } else {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    // Try to see if it is new request from the application, or refresh of some previous request
+    protected abstract boolean isNewRequest(AuthenticationSessionModel authSession, ClientModel clientFromRequest, String requestState);
+
+
+    protected static class AuthorizationEndpointChecks {
+        public final AuthenticationSessionModel authSession;
+        public final Response response;
+
+        private AuthorizationEndpointChecks(Response response) {
+            this.authSession = null;
+            this.response = response;
+        }
+
+        private AuthorizationEndpointChecks(AuthenticationSessionModel authSession) {
+            this.authSession = authSession;
+            this.response = null;
+        }
+    }
+
 }
\ No newline at end of file
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 6691712..0cd0219 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
@@ -41,6 +41,7 @@ import org.keycloak.protocol.oidc.utils.RedirectUtils;
 import org.keycloak.services.ErrorPageException;
 import org.keycloak.services.ServicesLogger;
 import org.keycloak.services.Urls;
+import org.keycloak.services.managers.AuthenticationSessionManager;
 import org.keycloak.services.messages.Messages;
 import org.keycloak.services.resources.LoginActionsService;
 import org.keycloak.services.util.CacheControlUtil;
@@ -67,7 +68,7 @@ public class AuthorizationEndpoint extends AuthorizationEndpointBase {
      * Prefix used to store additional HTTP GET params from original client request into {@link AuthenticationSessionModel} note to be available later in Authenticators, RequiredActions etc. Prefix is used to
      * prevent collisions with internally used notes.
      *
-     * @see AuthenticationSessionModel#getNote(String)
+     * @see AuthenticationSessionModel#getClientNote(String)
      */
     public static final String LOGIN_SESSION_NOTE_ADDITIONAL_REQ_PARAMS_PREFIX = "client_request_param_";
 
@@ -126,7 +127,13 @@ public class AuthorizationEndpoint extends AuthorizationEndpointBase {
             return errorResponse;
         }
 
-        createLoginSession();
+        AuthorizationEndpointChecks checks = getOrCreateAuthenticationSession(client, request.getState());
+        if (checks.response != null) {
+            return checks.response;
+        }
+
+        authenticationSession = checks.authSession;
+        updateAuthenticationSession();
 
         // So back button doesn't work
         CacheControlUtil.noBackButtonCacheControlHeader();
@@ -164,6 +171,7 @@ public class AuthorizationEndpoint extends AuthorizationEndpointBase {
         return this;
     }
 
+
     private void checkSsl() {
         if (!uriInfo.getBaseUri().getScheme().equals("https") && realm.getSslRequired().isRequired(clientConnection)) {
             event.error(Errors.SSL_REQUIRED);
@@ -358,39 +366,57 @@ public class AuthorizationEndpoint extends AuthorizationEndpointBase {
         }
     }
 
-    private void createLoginSession() {
-        authenticationSession = session.authenticationSessions().createAuthenticationSession(realm, client, true);
+
+    @Override
+    protected boolean isNewRequest(AuthenticationSessionModel authSession, ClientModel clientFromRequest, String stateFromRequest) {
+        if (stateFromRequest==null) {
+            return true;
+        }
+
+        // Check if it's different client
+        if (!clientFromRequest.equals(authSession.getClient())) {
+            return true;
+        }
+
+        // If state is same, we likely have the refresh of some previous request
+        String stateFromSession = authSession.getClientNote(OIDCLoginProtocol.STATE_PARAM);
+        return !stateFromRequest.equals(stateFromSession);
+    }
+
+
+    private void updateAuthenticationSession() {
         authenticationSession.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL);
         authenticationSession.setRedirectUri(redirectUri);
         authenticationSession.setAction(ClientSessionModel.Action.AUTHENTICATE.name());
-        authenticationSession.setNote(OIDCLoginProtocol.RESPONSE_TYPE_PARAM, request.getResponseType());
-        authenticationSession.setNote(OIDCLoginProtocol.REDIRECT_URI_PARAM, request.getRedirectUriParam());
-        authenticationSession.setNote(OIDCLoginProtocol.ISSUER, Urls.realmIssuer(uriInfo.getBaseUri(), realm.getName()));
-
-        if (request.getState() != null) authenticationSession.setNote(OIDCLoginProtocol.STATE_PARAM, request.getState());
-        if (request.getNonce() != null) authenticationSession.setNote(OIDCLoginProtocol.NONCE_PARAM, request.getNonce());
-        if (request.getMaxAge() != null) authenticationSession.setNote(OIDCLoginProtocol.MAX_AGE_PARAM, String.valueOf(request.getMaxAge()));
-        if (request.getScope() != null) authenticationSession.setNote(OIDCLoginProtocol.SCOPE_PARAM, request.getScope());
-        if (request.getLoginHint() != null) authenticationSession.setNote(OIDCLoginProtocol.LOGIN_HINT_PARAM, request.getLoginHint());
-        if (request.getPrompt() != null) authenticationSession.setNote(OIDCLoginProtocol.PROMPT_PARAM, request.getPrompt());
-        if (request.getIdpHint() != null) authenticationSession.setNote(AdapterConstants.KC_IDP_HINT, request.getIdpHint());
-        if (request.getResponseMode() != null) authenticationSession.setNote(OIDCLoginProtocol.RESPONSE_MODE_PARAM, request.getResponseMode());
+        authenticationSession.setClientNote(OIDCLoginProtocol.RESPONSE_TYPE_PARAM, request.getResponseType());
+        authenticationSession.setClientNote(OIDCLoginProtocol.REDIRECT_URI_PARAM, request.getRedirectUriParam());
+        authenticationSession.setClientNote(OIDCLoginProtocol.ISSUER, Urls.realmIssuer(uriInfo.getBaseUri(), realm.getName()));
+
+        if (request.getState() != null) authenticationSession.setClientNote(OIDCLoginProtocol.STATE_PARAM, request.getState());
+        if (request.getNonce() != null) authenticationSession.setClientNote(OIDCLoginProtocol.NONCE_PARAM, request.getNonce());
+        if (request.getMaxAge() != null) authenticationSession.setClientNote(OIDCLoginProtocol.MAX_AGE_PARAM, String.valueOf(request.getMaxAge()));
+        if (request.getScope() != null) authenticationSession.setClientNote(OIDCLoginProtocol.SCOPE_PARAM, request.getScope());
+        if (request.getLoginHint() != null) authenticationSession.setClientNote(OIDCLoginProtocol.LOGIN_HINT_PARAM, request.getLoginHint());
+        if (request.getPrompt() != null) authenticationSession.setClientNote(OIDCLoginProtocol.PROMPT_PARAM, request.getPrompt());
+        if (request.getIdpHint() != null) authenticationSession.setClientNote(AdapterConstants.KC_IDP_HINT, request.getIdpHint());
+        if (request.getResponseMode() != null) authenticationSession.setClientNote(OIDCLoginProtocol.RESPONSE_MODE_PARAM, request.getResponseMode());
 
         // https://tools.ietf.org/html/rfc7636#section-4
-        if (request.getCodeChallenge() != null) loginSession.setNote(OIDCLoginProtocol.CODE_CHALLENGE_PARAM, request.getCodeChallenge());
+        if (request.getCodeChallenge() != null) authenticationSession.setClientNote(OIDCLoginProtocol.CODE_CHALLENGE_PARAM, request.getCodeChallenge());
         if (request.getCodeChallengeMethod() != null) {
-            loginSession.setNote(OIDCLoginProtocol.CODE_CHALLENGE_METHOD_PARAM, request.getCodeChallengeMethod());
+            authenticationSession.setClientNote(OIDCLoginProtocol.CODE_CHALLENGE_METHOD_PARAM, request.getCodeChallengeMethod());
         } else {
-            loginSession.setNote(OIDCLoginProtocol.CODE_CHALLENGE_METHOD_PARAM, OIDCLoginProtocol.PKCE_METHOD_PLAIN);
+            authenticationSession.setClientNote(OIDCLoginProtocol.CODE_CHALLENGE_METHOD_PARAM, OIDCLoginProtocol.PKCE_METHOD_PLAIN);
         }
 
         if (request.getAdditionalReqParams() != null) {
             for (String paramName : request.getAdditionalReqParams().keySet()) {
-                authenticationSession.setNote(LOGIN_SESSION_NOTE_ADDITIONAL_REQ_PARAMS_PREFIX + paramName, request.getAdditionalReqParams().get(paramName));
+                authenticationSession.setClientNote(LOGIN_SESSION_NOTE_ADDITIONAL_REQ_PARAMS_PREFIX + paramName, request.getAdditionalReqParams().get(paramName));
             }
         }
     }
 
+
     private Response buildAuthorizationCodeAuthorizationResponse() {
         this.event.event(EventType.LOGIN);
         authenticationSession.setAuthNote(Details.AUTH_TYPE, CODE_AUTH_TYPE);
@@ -405,6 +431,7 @@ public class AuthorizationEndpoint extends AuthorizationEndpointBase {
         String flowId = flow.getId();
 
         AuthenticationProcessor processor = createProcessor(authenticationSession, flowId, LoginActionsService.REGISTRATION_PATH);
+        authenticationSession.setClientNote(APP_INITIATED_FLOW, LoginActionsService.REGISTRATION_PATH);
 
         return processor.authenticate();
     }
@@ -416,6 +443,7 @@ public class AuthorizationEndpoint extends AuthorizationEndpointBase {
         String flowId = flow.getId();
 
         AuthenticationProcessor processor = createProcessor(authenticationSession, flowId, LoginActionsService.RESET_CREDENTIALS_PATH);
+        authenticationSession.setClientNote(APP_INITIATED_FLOW, LoginActionsService.REGISTRATION_PATH);
 
         return processor.authenticate();
     }
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java
index 2a6c2dc..d564840 100644
--- a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java
@@ -47,6 +47,7 @@ import org.keycloak.services.ErrorResponseException;
 import org.keycloak.services.ServicesLogger;
 import org.keycloak.services.Urls;
 import org.keycloak.services.managers.AuthenticationManager;
+import org.keycloak.services.managers.AuthenticationSessionManager;
 import org.keycloak.services.managers.ClientManager;
 import org.keycloak.services.managers.ClientSessionCode;
 import org.keycloak.services.managers.RealmManager;
@@ -416,11 +417,11 @@ public class TokenEndpoint {
         }
         String scope = formParams.getFirst(OAuth2Constants.SCOPE);
 
-        AuthenticationSessionModel authSession = session.authenticationSessions().createAuthenticationSession(realm, client, false);
+        AuthenticationSessionModel authSession = new AuthenticationSessionManager(session).createAuthenticationSession(realm, client, false);
         authSession.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL);
         authSession.setAction(AuthenticatedClientSessionModel.Action.AUTHENTICATE.name());
-        authSession.setNote(OIDCLoginProtocol.ISSUER, Urls.realmIssuer(uriInfo.getBaseUri(), realm.getName()));
-        authSession.setNote(OIDCLoginProtocol.SCOPE_PARAM, scope);
+        authSession.setClientNote(OIDCLoginProtocol.ISSUER, Urls.realmIssuer(uriInfo.getBaseUri(), realm.getName()));
+        authSession.setClientNote(OIDCLoginProtocol.SCOPE_PARAM, scope);
 
         AuthenticationFlowModel flow = realm.getDirectGrantFlow();
         String flowId = flow.getId();
@@ -442,6 +443,9 @@ public class TokenEndpoint {
             throw new ErrorResponseException(OAuthErrorException.INVALID_GRANT, "Account is not fully set up", Response.Status.BAD_REQUEST);
 
         }
+
+        AuthenticationManager.setRolesAndMappersInSession(authSession);
+
         AuthenticatedClientSessionModel clientSession = processor.attachSession();
         UserSessionModel userSession = processor.getUserSession();
         updateUserSessionFromClientAuth(userSession);
@@ -492,14 +496,16 @@ public class TokenEndpoint {
 
         String scope = formParams.getFirst(OAuth2Constants.SCOPE);
 
-        AuthenticationSessionModel authSession = session.authenticationSessions().createAuthenticationSession(realm, client, false);
+        AuthenticationSessionModel authSession = new AuthenticationSessionManager(session).createAuthenticationSession(realm, client, false);
+        authSession.setAuthenticatedUser(clientUser);
         authSession.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL);
-        authSession.setNote(OIDCLoginProtocol.ISSUER, Urls.realmIssuer(uriInfo.getBaseUri(), realm.getName()));
-        authSession.setNote(OIDCLoginProtocol.SCOPE_PARAM, scope);
+        authSession.setClientNote(OIDCLoginProtocol.ISSUER, Urls.realmIssuer(uriInfo.getBaseUri(), realm.getName()));
+        authSession.setClientNote(OIDCLoginProtocol.SCOPE_PARAM, scope);
 
-        UserSessionModel userSession = session.sessions().createUserSession(realm, clientUser, clientUsername, clientConnection.getRemoteAddr(), ServiceAccountConstants.CLIENT_AUTH, false, null, null);
+        UserSessionModel userSession = session.sessions().createUserSession(authSession.getId(), realm, clientUser, clientUsername, clientConnection.getRemoteAddr(), ServiceAccountConstants.CLIENT_AUTH, false, null, null);
         event.session(userSession);
 
+        AuthenticationManager.setRolesAndMappersInSession(authSession);
         AuthenticatedClientSessionModel clientSession = TokenManager.attachAuthenticationSession(session, userSession, authSession);
 
         // Notes about client details
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocol.java b/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocol.java
index 59b7835..13d24a7 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocol.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocol.java
@@ -36,6 +36,7 @@ import org.keycloak.protocol.oidc.utils.OIDCResponseType;
 import org.keycloak.representations.AccessTokenResponse;
 import org.keycloak.services.ServicesLogger;
 import org.keycloak.services.managers.AuthenticationManager;
+import org.keycloak.services.managers.AuthenticationSessionManager;
 import org.keycloak.services.managers.ClientSessionCode;
 import org.keycloak.services.managers.ResourceAdminManager;
 import org.keycloak.sessions.CommonClientSessionModel;
@@ -130,9 +131,7 @@ public class OIDCLoginProtocol implements LoginProtocol {
 
     }
 
-    private void setupResponseTypeAndMode(CommonClientSessionModel clientSession) {
-        String responseType = clientSession.getNote(OIDCLoginProtocol.RESPONSE_TYPE_PARAM);
-        String responseMode = clientSession.getNote(OIDCLoginProtocol.RESPONSE_MODE_PARAM);
+    private void setupResponseTypeAndMode(String responseType, String responseMode) {
         this.responseType = OIDCResponseType.parse(responseType);
         this.responseMode = OIDCResponseMode.parse(responseMode, this.responseType);
         this.event.detail(Details.RESPONSE_TYPE, responseType);
@@ -171,9 +170,12 @@ public class OIDCLoginProtocol implements LoginProtocol {
 
 
     @Override
-    public Response authenticated(UserSessionModel userSession, ClientSessionCode<AuthenticatedClientSessionModel> accessCode) {
-        AuthenticatedClientSessionModel clientSession = accessCode.getClientSession();
-        setupResponseTypeAndMode(clientSession);
+    public Response authenticated(UserSessionModel userSession, AuthenticatedClientSessionModel clientSession) {
+        ClientSessionCode<AuthenticatedClientSessionModel> accessCode = new ClientSessionCode<>(session, realm, clientSession);
+
+        String responseTypeParam = clientSession.getNote(OIDCLoginProtocol.RESPONSE_TYPE_PARAM);
+        String responseModeParam = clientSession.getNote(OIDCLoginProtocol.RESPONSE_MODE_PARAM);
+        setupResponseTypeAndMode(responseTypeParam, responseModeParam);
 
         String redirect = clientSession.getRedirectUri();
         OIDCRedirectUriBuilder redirectUri = OIDCRedirectUriBuilder.fromUri(redirect, responseMode);
@@ -230,15 +232,16 @@ public class OIDCLoginProtocol implements LoginProtocol {
 
     @Override
     public Response sendError(AuthenticationSessionModel authSession, Error error) {
-        setupResponseTypeAndMode(authSession);
+        String responseTypeParam = authSession.getClientNote(OIDCLoginProtocol.RESPONSE_TYPE_PARAM);
+        String responseModeParam = authSession.getClientNote(OIDCLoginProtocol.RESPONSE_MODE_PARAM);
+        setupResponseTypeAndMode(responseTypeParam, responseModeParam);
 
         String redirect = authSession.getRedirectUri();
-        String state = authSession.getNote(OIDCLoginProtocol.STATE_PARAM);
+        String state = authSession.getClientNote(OIDCLoginProtocol.STATE_PARAM);
         OIDCRedirectUriBuilder redirectUri = OIDCRedirectUriBuilder.fromUri(redirect, responseMode).addParam(OAuth2Constants.ERROR, translateError(error));
         if (state != null)
             redirectUri.addParam(OAuth2Constants.STATE, state);
-        session.authenticationSessions().removeAuthenticationSession(realm, authSession);
-        RestartLoginCookie.expireRestartCookie(realm, session.getContext().getConnection(), uriInfo);
+        new AuthenticationSessionManager(session).removeAuthenticationSession(realm, authSession, true);
         return redirectUri.build();
     }
 
@@ -296,13 +299,13 @@ public class OIDCLoginProtocol implements LoginProtocol {
     }
 
     protected boolean isPromptLogin(AuthenticationSessionModel authSession) {
-        String prompt = authSession.getNote(OIDCLoginProtocol.PROMPT_PARAM);
+        String prompt = authSession.getClientNote(OIDCLoginProtocol.PROMPT_PARAM);
         return TokenUtil.hasPrompt(prompt, OIDCLoginProtocol.PROMPT_VALUE_LOGIN);
     }
 
     protected boolean isAuthTimeExpired(UserSessionModel userSession, AuthenticationSessionModel authSession) {
         String authTime = userSession.getNote(AuthenticationManager.AUTH_TIME);
-        String maxAge = authSession.getNote(OIDCLoginProtocol.MAX_AGE_PARAM);
+        String maxAge = authSession.getClientNote(OIDCLoginProtocol.MAX_AGE_PARAM);
         if (maxAge == null) {
             return false;
         }
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java b/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java
index cd7c65c..93df9b0 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java
@@ -57,6 +57,7 @@ import org.keycloak.representations.IDToken;
 import org.keycloak.representations.RefreshToken;
 import org.keycloak.services.ErrorResponseException;
 import org.keycloak.services.managers.AuthenticationManager;
+import org.keycloak.services.managers.AuthenticationSessionManager;
 import org.keycloak.services.managers.ClientSessionCode;
 import org.keycloak.services.managers.UserSessionManager;
 import org.keycloak.sessions.AuthenticationSessionModel;
@@ -358,14 +359,18 @@ public class TokenManager {
     public static AuthenticatedClientSessionModel attachAuthenticationSession(KeycloakSession session, UserSessionModel userSession, AuthenticationSessionModel authSession) {
         ClientModel client = authSession.getClient();
 
-        AuthenticatedClientSessionModel clientSession = session.sessions().createClientSession(userSession.getRealm(), client, userSession);
+        AuthenticatedClientSessionModel clientSession = userSession.getAuthenticatedClientSessions().get(client.getId());
+        if (clientSession == null) {
+            clientSession = session.sessions().createClientSession(userSession.getRealm(), client, userSession);
+        }
+
         clientSession.setRedirectUri(authSession.getRedirectUri());
         clientSession.setProtocol(authSession.getProtocol());
 
         clientSession.setRoles(authSession.getRoles());
         clientSession.setProtocolMappers(authSession.getProtocolMappers());
 
-        Map<String, String> transferredNotes = authSession.getNotes();
+        Map<String, String> transferredNotes = authSession.getClientNotes();
         for (Map.Entry<String, String> entry : transferredNotes.entrySet()) {
             clientSession.setNote(entry.getKey(), entry.getValue());
         }
@@ -378,7 +383,7 @@ public class TokenManager {
         clientSession.setTimestamp(Time.currentTime());
 
         // Remove authentication session now
-        session.authenticationSessions().removeAuthenticationSession(userSession.getRealm(), authSession);
+        new AuthenticationSessionManager(session).removeAuthenticationSession(userSession.getRealm(), authSession, true);
 
         return clientSession;
     }
diff --git a/services/src/main/java/org/keycloak/protocol/RestartLoginCookie.java b/services/src/main/java/org/keycloak/protocol/RestartLoginCookie.java
index 22d764d..0e488e1 100644
--- a/services/src/main/java/org/keycloak/protocol/RestartLoginCookie.java
+++ b/services/src/main/java/org/keycloak/protocol/RestartLoginCookie.java
@@ -28,6 +28,7 @@ import org.keycloak.models.KeyManager;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.RealmModel;
 import org.keycloak.services.managers.AuthenticationManager;
+import org.keycloak.services.managers.AuthenticationSessionManager;
 import org.keycloak.services.util.CookieHelper;
 import org.keycloak.sessions.AuthenticationSessionModel;
 
@@ -38,7 +39,7 @@ import java.util.HashMap;
 import java.util.Map;
 
 /**
- * This is an an encoded token that is stored as a cookie so that if there is a client timeout, then the client session
+ * This is an an encoded token that is stored as a cookie so that if there is a client timeout, then the authentication session
  * can be restarted.
  *
  * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@@ -47,8 +48,6 @@ import java.util.Map;
 public class RestartLoginCookie {
     private static final Logger logger = Logger.getLogger(RestartLoginCookie.class);
     public static final String KC_RESTART = "KC_RESTART";
-    @JsonProperty("cs")
-    protected String authenticationSession;
 
     @JsonProperty("cid")
     protected String clientId;
@@ -65,14 +64,6 @@ public class RestartLoginCookie {
     @JsonProperty("notes")
     protected Map<String, String> notes = new HashMap<>();
 
-    public String getAuthenticationSession() {
-        return authenticationSession;
-    }
-
-    public void setAuthenticationSession(String authenticationSession) {
-        this.authenticationSession = authenticationSession;
-    }
-
     public Map<String, String> getNotes() {
         return notes;
     }
@@ -128,8 +119,7 @@ public class RestartLoginCookie {
         this.clientId = clientSession.getClient().getClientId();
         this.authMethod = clientSession.getProtocol();
         this.redirectUri = clientSession.getRedirectUri();
-        this.authenticationSession = clientSession.getId();
-        for (Map.Entry<String, String> entry : clientSession.getNotes().entrySet()) {
+        for (Map.Entry<String, String> entry : clientSession.getClientNotes().entrySet()) {
             notes.put(entry.getKey(), entry.getValue());
         }
     }
@@ -150,10 +140,6 @@ public class RestartLoginCookie {
 
 
     public static AuthenticationSessionModel restartSession(KeycloakSession session, RealmModel realm) throws Exception {
-        return restartSessionByClientSession(session, realm);
-    }
-
-    private static AuthenticationSessionModel restartSessionByClientSession(KeycloakSession session, RealmModel realm) throws Exception {
         Cookie cook = session.getContext().getRequestHeaders().getCookies().get(KC_RESTART);
         if (cook ==  null) {
             logger.debug("KC_RESTART cookie doesn't exist");
@@ -171,12 +157,12 @@ public class RestartLoginCookie {
         ClientModel client = realm.getClientByClientId(cookie.getClientId());
         if (client == null) return null;
 
-        AuthenticationSessionModel authSession = session.authenticationSessions().createAuthenticationSession(realm, client, true);
+        AuthenticationSessionModel authSession = new AuthenticationSessionManager(session).createAuthenticationSession(realm, client, true);
         authSession.setProtocol(cookie.getAuthMethod());
         authSession.setRedirectUri(cookie.getRedirectUri());
         authSession.setAction(cookie.getAction());
         for (Map.Entry<String, String> entry : cookie.getNotes().entrySet()) {
-            authSession.setNote(entry.getKey(), entry.getValue());
+            authSession.setClientNote(entry.getKey(), entry.getValue());
         }
 
         return authSession;
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 db43a75..10e45a2 100755
--- a/services/src/main/java/org/keycloak/protocol/saml/SamlProtocol.java
+++ b/services/src/main/java/org/keycloak/protocol/saml/SamlProtocol.java
@@ -57,6 +57,7 @@ import org.keycloak.saml.common.exceptions.ProcessingException;
 import org.keycloak.saml.common.util.XmlKeyInfoKeyNameTransformer;
 import org.keycloak.saml.processing.core.util.KeycloakKeySamlExtensionGenerator;
 import org.keycloak.services.ErrorPage;
+import org.keycloak.services.managers.AuthenticationSessionManager;
 import org.keycloak.services.managers.ClientSessionCode;
 import org.keycloak.services.managers.ResourceAdminManager;
 import org.keycloak.services.messages.Messages;
@@ -177,7 +178,7 @@ public class SamlProtocol implements LoginProtocol {
             } else {
                 SAML2ErrorResponseBuilder builder = new SAML2ErrorResponseBuilder().destination(authSession.getRedirectUri()).issuer(getResponseIssuer(realm)).status(translateErrorToSAMLStatus(error).get());
                 try {
-                    JaxrsSAML2BindingBuilder binding = new JaxrsSAML2BindingBuilder().relayState(authSession.getNote(GeneralConstants.RELAY_STATE));
+                    JaxrsSAML2BindingBuilder binding = new JaxrsSAML2BindingBuilder().relayState(authSession.getClientNote(GeneralConstants.RELAY_STATE));
                     SamlClient samlClient = new SamlClient(client);
                     KeyManager keyManager = session.keys();
                     if (samlClient.requiresRealmSignature()) {
@@ -206,8 +207,7 @@ public class SamlProtocol implements LoginProtocol {
                 }
             }
         } finally {
-            RestartLoginCookie.expireRestartCookie(realm, session.getContext().getConnection(), uriInfo);
-            session.authenticationSessions().removeAuthenticationSession(realm, authSession);
+            new AuthenticationSessionManager(session).removeAuthenticationSession(realm, authSession, true);
         }
     }
 
@@ -250,10 +250,16 @@ public class SamlProtocol implements LoginProtocol {
         return RealmsResource.realmBaseUrl(uriInfo).build(realm.getName()).toString();
     }
 
-    protected boolean isPostBinding(CommonClientSessionModel authSession) {
+    protected boolean isPostBinding(AuthenticationSessionModel authSession) {
         ClientModel client = authSession.getClient();
         SamlClient samlClient = new SamlClient(client);
-        return SamlProtocol.SAML_POST_BINDING.equals(authSession.getNote(SamlProtocol.SAML_BINDING)) || samlClient.forcePostBinding();
+        return SamlProtocol.SAML_POST_BINDING.equals(authSession.getClientNote(SamlProtocol.SAML_BINDING)) || samlClient.forcePostBinding();
+    }
+
+    protected boolean isPostBinding(AuthenticatedClientSessionModel clientSession) {
+        ClientModel client = clientSession.getClient();
+        SamlClient samlClient = new SamlClient(client);
+        return SamlProtocol.SAML_POST_BINDING.equals(clientSession.getNote(SamlProtocol.SAML_BINDING)) || samlClient.forcePostBinding();
     }
 
     public static boolean isLogoutPostBindingForInitiator(UserSessionModel session) {
@@ -286,7 +292,7 @@ public class SamlProtocol implements LoginProtocol {
         return (logoutRedirectUrl == null || logoutRedirectUrl.trim().isEmpty());
     }
 
-    protected String getNameIdFormat(SamlClient samlClient, CommonClientSessionModel clientSession) {
+    protected String getNameIdFormat(SamlClient samlClient, AuthenticatedClientSessionModel clientSession) {
         String nameIdFormat = clientSession.getNote(GeneralConstants.NAMEID_FORMAT);
 
         boolean forceFormat = samlClient.forceNameIDFormat();
@@ -353,8 +359,8 @@ public class SamlProtocol implements LoginProtocol {
     }
 
     @Override
-    public Response authenticated(UserSessionModel userSession, ClientSessionCode<AuthenticatedClientSessionModel> accessCode) {
-        AuthenticatedClientSessionModel clientSession = accessCode.getClientSession();
+    public Response authenticated(UserSessionModel userSession, AuthenticatedClientSessionModel clientSession) {
+        ClientSessionCode<AuthenticatedClientSessionModel> accessCode = new ClientSessionCode<>(session, realm, clientSession);
         ClientModel client = clientSession.getClient();
         SamlClient samlClient = new SamlClient(client);
         String requestID = clientSession.getNote(SAML_REQUEST_ID);
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 29daf1e..f81d25d 100755
--- a/services/src/main/java/org/keycloak/protocol/saml/SamlService.java
+++ b/services/src/main/java/org/keycloak/protocol/saml/SamlService.java
@@ -55,6 +55,7 @@ import org.keycloak.saml.common.constants.JBossSAMLURIConstants;
 import org.keycloak.saml.processing.core.saml.v2.common.SAMLDocumentHolder;
 import org.keycloak.services.ErrorPage;
 import org.keycloak.services.managers.AuthenticationManager;
+import org.keycloak.services.managers.AuthenticationSessionManager;
 import org.keycloak.services.messages.Messages;
 import org.keycloak.services.resources.RealmsResource;
 import org.keycloak.services.util.CacheControlUtil;
@@ -89,7 +90,7 @@ import org.keycloak.saml.processing.core.util.KeycloakKeySamlExtensionGenerator;
 import org.keycloak.sessions.AuthenticationSessionModel;
 
 /**
- * Resource class for the oauth/openid connect token service
+ * Resource class for the saml connect token service
  *
  * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
  * @version $Revision: 1 $
@@ -101,6 +102,8 @@ public class SamlService extends AuthorizationEndpointBase {
     @Context
     protected KeycloakSession session;
 
+    private String requestRelayState;
+
     public SamlService(RealmModel realm, EventBuilder event) {
         super(realm, event);
     }
@@ -271,13 +274,19 @@ public class SamlService extends AuthorizationEndpointBase {
                 return ErrorPage.error(session, Messages.INVALID_REDIRECT_URI);
             }
 
-            AuthenticationSessionModel authSession = session.authenticationSessions().createAuthenticationSession(realm, client, true);
+            AuthorizationEndpointChecks checks = getOrCreateAuthenticationSession(client, relayState);
+            if (checks.response != null) {
+                return checks.response;
+            }
+
+            AuthenticationSessionModel authSession = checks.authSession;
+
             authSession.setProtocol(SamlProtocol.LOGIN_PROTOCOL);
             authSession.setRedirectUri(redirect);
             authSession.setAction(ClientSessionModel.Action.AUTHENTICATE.name());
-            authSession.setNote(SamlProtocol.SAML_BINDING, bindingType);
-            authSession.setNote(GeneralConstants.RELAY_STATE, relayState);
-            authSession.setNote(SamlProtocol.SAML_REQUEST_ID, requestAbstractType.getID());
+            authSession.setClientNote(SamlProtocol.SAML_BINDING, bindingType);
+            authSession.setClientNote(GeneralConstants.RELAY_STATE, relayState);
+            authSession.setClientNote(SamlProtocol.SAML_REQUEST_ID, requestAbstractType.getID());
 
             // Handle NameIDPolicy from SP
             NameIDPolicyType nameIdPolicy = requestAbstractType.getNameIDPolicy();
@@ -286,7 +295,7 @@ public class SamlService extends AuthorizationEndpointBase {
                 String nameIdFormat = nameIdFormatUri.toString();
                 // TODO: Handle AllowCreate too, relevant for persistent NameID.
                 if (isSupportedNameIdFormat(nameIdFormat)) {
-                    authSession.setNote(GeneralConstants.NAMEID_FORMAT, nameIdFormat);
+                    authSession.setClientNote(GeneralConstants.NAMEID_FORMAT, nameIdFormat);
                 } else {
                     event.detail(Details.REASON, "unsupported_nameid_format");
                     event.error(Errors.INVALID_SAML_AUTHN_REQUEST);
@@ -302,7 +311,7 @@ public class SamlService extends AuthorizationEndpointBase {
                     BaseIDAbstractType baseID = subject.getSubType().getBaseID();
                     if (baseID != null && baseID instanceof NameIDType) {
                         NameIDType nameID = (NameIDType) baseID;
-                        authSession.setNote(OIDCLoginProtocol.LOGIN_HINT_PARAM, nameID.getValue());
+                        authSession.setClientNote(OIDCLoginProtocol.LOGIN_HINT_PARAM, nameID.getValue());
                     }
 
                 }
@@ -443,6 +452,16 @@ public class SamlService extends AuthorizationEndpointBase {
                 return !realm.getSslRequired().isRequired(clientConnection);
             }
         }
+
+        public Response execute(String samlRequest, String samlResponse, String relayState) {
+            Response response = basicChecks(samlRequest, samlResponse);
+            if (response != null)
+                return response;
+            if (samlRequest != null)
+                return handleSamlRequest(samlRequest, relayState);
+            else
+                return handleSamlResponse(samlResponse, relayState);
+        }
     }
 
     protected class PostBindingProtocol extends BindingProtocol {
@@ -467,16 +486,6 @@ public class SamlService extends AuthorizationEndpointBase {
             return SamlProtocol.SAML_POST_BINDING;
         }
 
-        public Response execute(String samlRequest, String samlResponse, String relayState) {
-            Response response = basicChecks(samlRequest, samlResponse);
-            if (response != null)
-                return response;
-            if (samlRequest != null)
-                return handleSamlRequest(samlRequest, relayState);
-            else
-                return handleSamlResponse(samlResponse, relayState);
-        }
-
     }
 
     protected class RedirectBindingProtocol extends BindingProtocol {
@@ -507,16 +516,6 @@ public class SamlService extends AuthorizationEndpointBase {
             return SamlProtocol.SAML_REDIRECT_BINDING;
         }
 
-        public Response execute(String samlRequest, String samlResponse, String relayState) {
-            Response response = basicChecks(samlRequest, samlResponse);
-            if (response != null)
-                return response;
-            if (samlRequest != null)
-                return handleSamlRequest(samlRequest, relayState);
-            else
-                return handleSamlResponse(samlResponse, relayState);
-        }
-
     }
 
     protected Response newBrowserAuthentication(AuthenticationSessionModel authSession, boolean isPassive, boolean redirectToAuthentication) {
@@ -616,7 +615,7 @@ public class SamlService extends AuthorizationEndpointBase {
             return ErrorPage.error(session, Messages.INVALID_REDIRECT_URI);
         }
 
-        AuthenticationSessionModel authSession = createLoginSessionForIdpInitiatedSso(this.session, this.realm, client, relayState);
+        AuthenticationSessionModel authSession = getOrCreateLoginSessionForIdpInitiatedSso(this.session, this.realm, client, relayState);
 
         return newBrowserAuthentication(authSession, false, false);
     }
@@ -632,7 +631,7 @@ public class SamlService extends AuthorizationEndpointBase {
      * @param relayState Optional relay state - free field as per SAML specification
      * @return
      */
-    public static AuthenticationSessionModel createLoginSessionForIdpInitiatedSso(KeycloakSession session, RealmModel realm, ClientModel client, String relayState) {
+    public AuthenticationSessionModel getOrCreateLoginSessionForIdpInitiatedSso(KeycloakSession session, RealmModel realm, ClientModel client, String relayState) {
         String bindingType = SamlProtocol.SAML_POST_BINDING;
         if (client.getManagementUrl() == null && client.getAttribute(SamlProtocol.SAML_ASSERTION_CONSUMER_URL_POST_ATTRIBUTE) == null && client.getAttribute(SamlProtocol.SAML_ASSERTION_CONSUMER_URL_REDIRECT_ATTRIBUTE) != null) {
             bindingType = SamlProtocol.SAML_REDIRECT_BINDING;
@@ -648,23 +647,49 @@ public class SamlService extends AuthorizationEndpointBase {
             redirect = client.getManagementUrl();
         }
 
-        AuthenticationSessionModel authSession = session.authenticationSessions().createAuthenticationSession(realm, client, true);
+        AuthorizationEndpointChecks checks = getOrCreateAuthenticationSession(client, null);
+        if (checks.response != null) {
+            throw new IllegalStateException("Not expected to detect re-sent request for IDP initiated SSO");
+        }
+
+        AuthenticationSessionModel authSession = checks.authSession;
         authSession.setProtocol(SamlProtocol.LOGIN_PROTOCOL);
         authSession.setAction(ClientSessionModel.Action.AUTHENTICATE.name());
-        authSession.setNote(SamlProtocol.SAML_BINDING, SamlProtocol.SAML_POST_BINDING);
-        authSession.setNote(SamlProtocol.SAML_IDP_INITIATED_LOGIN, "true");
+        authSession.setClientNote(SamlProtocol.SAML_BINDING, SamlProtocol.SAML_POST_BINDING);
+        authSession.setClientNote(SamlProtocol.SAML_IDP_INITIATED_LOGIN, "true");
         authSession.setRedirectUri(redirect);
 
         if (relayState == null) {
             relayState = client.getAttribute(SamlProtocol.SAML_IDP_INITIATED_SSO_RELAY_STATE);
         }
         if (relayState != null && !relayState.trim().equals("")) {
-            authSession.setNote(GeneralConstants.RELAY_STATE, relayState);
+            authSession.setClientNote(GeneralConstants.RELAY_STATE, relayState);
         }
 
         return authSession;
     }
 
+
+    @Override
+    protected boolean isNewRequest(AuthenticationSessionModel authSession, ClientModel clientFromRequest, String requestRelayState) {
+        // No support of browser "refresh" or "back" buttons for SAML IDP initiated SSO. So always treat as new request
+        String idpInitiated = authSession.getClientNote(SamlProtocol.SAML_IDP_INITIATED_LOGIN);
+        if (Boolean.parseBoolean(idpInitiated)) {
+            return true;
+        }
+
+        if (requestRelayState == null) {
+            return true;
+        }
+
+        // Check if it's different client
+        if (!clientFromRequest.equals(authSession.getClient())) {
+            return true;
+        }
+
+        return !requestRelayState.equals(authSession.getClientNote(GeneralConstants.RELAY_STATE));
+    }
+
     @POST
     @NoCache
     @Consumes({"application/soap+xml",MediaType.TEXT_XML})
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 10b5e3d..0ba5d77 100755
--- a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
@@ -39,6 +39,7 @@ import org.keycloak.jose.jws.AlgorithmType;
 import org.keycloak.jose.jws.JWSBuilder;
 import org.keycloak.models.AuthenticatedClientSessionModel;
 import org.keycloak.models.ClientModel;
+import org.keycloak.models.ClientSessionModel;
 import org.keycloak.models.ClientTemplateModel;
 import org.keycloak.models.KeyManager;
 import org.keycloak.models.KeycloakSession;
@@ -52,16 +53,19 @@ import org.keycloak.models.UserSessionModel;
 import org.keycloak.models.utils.KeycloakModelUtils;
 import org.keycloak.protocol.LoginProtocol;
 import org.keycloak.protocol.LoginProtocol.Error;
+import org.keycloak.protocol.RestartLoginCookie;
 import org.keycloak.protocol.oidc.TokenManager;
 import org.keycloak.representations.AccessToken;
 import org.keycloak.services.ServicesLogger;
 import org.keycloak.services.Urls;
 import org.keycloak.services.messages.Messages;
 import org.keycloak.services.resources.IdentityBrokerService;
+import org.keycloak.services.resources.LoginActionsService;
 import org.keycloak.services.resources.RealmsResource;
 import org.keycloak.services.util.CookieHelper;
 import org.keycloak.services.util.P3PHelper;
 import org.keycloak.sessions.AuthenticationSessionModel;
+import org.keycloak.sessions.CommonClientSessionModel;
 
 import javax.crypto.SecretKey;
 import javax.ws.rs.core.Cookie;
@@ -69,6 +73,7 @@ import javax.ws.rs.core.HttpHeaders;
 import javax.ws.rs.core.MultivaluedMap;
 import javax.ws.rs.core.NewCookie;
 import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriBuilder;
 import javax.ws.rs.core.UriInfo;
 import java.net.URI;
 import java.security.PublicKey;
@@ -88,6 +93,9 @@ public class AuthenticationManager {
     public static final String SET_REDIRECT_URI_AFTER_REQUIRED_ACTIONS= "SET_REDIRECT_URI_AFTER_REQUIRED_ACTIONS";
     public static final String END_AFTER_REQUIRED_ACTIONS = "END_AFTER_REQUIRED_ACTIONS";
 
+    // Last authenticated client in userSession.
+    public static final String LAST_AUTHENTICATED_CLIENT = "LAST_AUTHENTICATED_CLIENT";
+
     // userSession note with authTime (time when authentication flow including requiredActions was finished)
     public static final String AUTH_TIME = "AUTH_TIME";
     // clientSession note with flag that clientSession was authenticated through SSO cookie
@@ -470,7 +478,9 @@ public class AuthenticationManager {
             userSession.setNote(AUTH_TIME, String.valueOf(authTime));
         }
 
-        return protocol.authenticated(userSession, new ClientSessionCode<>(session, realm, clientSession));
+        userSession.setNote(LAST_AUTHENTICATED_CLIENT, clientSession.getClient().getId());
+
+        return protocol.authenticated(userSession, clientSession);
 
     }
 
@@ -485,11 +495,32 @@ public class AuthenticationManager {
                                                   HttpRequest request, UriInfo uriInfo, EventBuilder event) {
         Response requiredAction = actionRequired(session, authSession, clientConnection, request, uriInfo, event);
         if (requiredAction != null) return requiredAction;
-        return finishedRequiredActions(session, authSession, clientConnection, request, uriInfo, event);
+        return finishedRequiredActions(session, authSession, null, clientConnection, request, uriInfo, event);
 
     }
 
-    public static Response finishedRequiredActions(KeycloakSession session, AuthenticationSessionModel authSession,
+
+    public static Response redirectToRequiredActions(KeycloakSession session, RealmModel realm, AuthenticationSessionModel authSession, UriInfo uriInfo, String requiredAction) {
+        // redirect to non-action url so browser refresh button works without reposting past data
+        ClientSessionCode<AuthenticationSessionModel> accessCode = new ClientSessionCode<>(session, realm, authSession);
+        accessCode.setAction(ClientSessionModel.Action.REQUIRED_ACTIONS.name());
+        authSession.setAuthNote(AuthenticationProcessor.CURRENT_FLOW_PATH, LoginActionsService.REQUIRED_ACTION);
+        authSession.setAuthNote(AuthenticationProcessor.CURRENT_AUTHENTICATION_EXECUTION, requiredAction);
+
+        UriBuilder uriBuilder = LoginActionsService.loginActionsBaseUrl(uriInfo)
+                .path(LoginActionsService.REQUIRED_ACTION);
+
+        if (requiredAction != null) {
+            uriBuilder.queryParam("execution", requiredAction);
+        }
+
+        URI redirect = uriBuilder.build(realm.getName());
+        return Response.status(302).location(redirect).build();
+
+    }
+
+
+    public static Response finishedRequiredActions(KeycloakSession session, AuthenticationSessionModel authSession, UserSessionModel userSession,
                                                    ClientConnection clientConnection, HttpRequest request, UriInfo uriInfo, EventBuilder event) {
         if (authSession.getAuthNote(END_AFTER_REQUIRED_ACTIONS) != null) {
             LoginFormsProvider infoPage = session.getProvider(LoginFormsProvider.class)
@@ -506,10 +537,12 @@ public class AuthenticationManager {
                     .createInfoPage();
             return response;
 
+            // TODO:mposolda doublecheck if restart-cookie and authentication session are cleared in this flow
+
         }
         RealmModel realm = authSession.getRealm();
 
-        AuthenticatedClientSessionModel clientSession = AuthenticationProcessor.attachSession(authSession, null, session, realm, clientConnection, event);
+        AuthenticatedClientSessionModel clientSession = AuthenticationProcessor.attachSession(authSession, userSession, session, realm, clientConnection, event);
 
         event.event(EventType.LOGIN);
         event.session(clientSession.getUserSession());
@@ -517,16 +550,22 @@ public class AuthenticationManager {
         return redirectAfterSuccessfulFlow(session, realm, clientSession.getUserSession(), clientSession, request, uriInfo, clientConnection, event, authSession.getProtocol());
     }
 
-    public static boolean isActionRequired(final KeycloakSession session, final AuthenticationSessionModel authSession,
-                                           final ClientConnection clientConnection,
-                                           final HttpRequest request, final UriInfo uriInfo, final EventBuilder event) {
+    // Return null if action is not required. Or the name of the requiredAction in case it is required.
+    public static String nextRequiredAction(final KeycloakSession session, final AuthenticationSessionModel authSession,
+                                            final ClientConnection clientConnection,
+                                            final HttpRequest request, final UriInfo uriInfo, final EventBuilder event) {
         final RealmModel realm = authSession.getRealm();
         final UserModel user = authSession.getAuthenticatedUser();
         final ClientModel client = authSession.getClient();
 
         evaluateRequiredActionTriggers(session, authSession, clientConnection, request, uriInfo, event, realm, user);
 
-        if (!user.getRequiredActions().isEmpty() || !authSession.getRequiredActions().isEmpty()) return true;
+        if (!user.getRequiredActions().isEmpty()) {
+            return user.getRequiredActions().iterator().next();
+        }
+        if (!authSession.getRequiredActions().isEmpty()) {
+            return authSession.getRequiredActions().iterator().next();
+        }
 
         if (client.isConsentRequired()) {
 
@@ -539,13 +578,13 @@ public class AuthenticationManager {
                 if (grantedConsent != null && grantedConsent.isRoleGranted(r)) {
                     continue;
                 }
-                return true;
+                return CommonClientSessionModel.Action.OAUTH_GRANT.name();
              }
 
             for (ProtocolMapperModel protocolMapper : accessCode.getRequestedProtocolMappers()) {
                 if (protocolMapper.isConsentRequired() && protocolMapper.getConsentText() != null) {
                     if (grantedConsent == null || !grantedConsent.isProtocolMapperGranted(protocolMapper)) {
-                        return true;
+                        return CommonClientSessionModel.Action.OAUTH_GRANT.name();
                     }
                 }
             }
@@ -554,7 +593,7 @@ public class AuthenticationManager {
         } else {
             event.detail(Details.CONSENT, Details.CONSENT_VALUE_NO_CONSENT_REQUIRED);
         }
-        return false;
+        return null;
 
     }
 
@@ -617,7 +656,7 @@ public class AuthenticationManager {
                 accessCode.
 
                         setAction(AuthenticatedClientSessionModel.Action.REQUIRED_ACTIONS.name());
-                authSession.setAuthNote(CURRENT_REQUIRED_ACTION, AuthenticatedClientSessionModel.Action.OAUTH_GRANT.name());
+                authSession.setAuthNote(AuthenticationProcessor.CURRENT_AUTHENTICATION_EXECUTION, AuthenticatedClientSessionModel.Action.OAUTH_GRANT.name());
 
                 return session.getProvider(LoginFormsProvider.class)
                         .setClientSessionCode(accessCode.getCode())
@@ -641,7 +680,7 @@ public class AuthenticationManager {
 
         Set<String> requestedRoles = new HashSet<String>();
         // todo scope param protocol independent
-        String scopeParam = authSession.getNote(OAuth2Constants.SCOPE);
+        String scopeParam = authSession.getClientNote(OAuth2Constants.SCOPE);
         for (RoleModel r : TokenManager.getAccess(scopeParam, true, client, user)) {
             requestedRoles.add(r.getId());
         }
@@ -697,7 +736,7 @@ public class AuthenticationManager {
                 return response;
             }
             else if (context.getStatus() == RequiredActionContext.Status.CHALLENGE) {
-                authSession.setAuthNote(CURRENT_REQUIRED_ACTION, model.getProviderId());
+                authSession.setAuthNote(AuthenticationProcessor.CURRENT_AUTHENTICATION_EXECUTION, model.getProviderId());
                 return context.getChallenge();
             }
             else if (context.getStatus() == RequiredActionContext.Status.SUCCESS) {
diff --git a/services/src/main/java/org/keycloak/services/managers/AuthenticationSessionManager.java b/services/src/main/java/org/keycloak/services/managers/AuthenticationSessionManager.java
new file mode 100644
index 0000000..04271f1
--- /dev/null
+++ b/services/src/main/java/org/keycloak/services/managers/AuthenticationSessionManager.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.services.managers;
+
+import javax.ws.rs.core.UriInfo;
+
+import org.jboss.logging.Logger;
+import org.keycloak.common.ClientConnection;
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserSessionModel;
+import org.keycloak.protocol.RestartLoginCookie;
+import org.keycloak.services.util.CookieHelper;
+import org.keycloak.sessions.AuthenticationSessionModel;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class AuthenticationSessionManager {
+
+    private static final String AUTH_SESSION_ID = "AUTH_SESSION_ID";
+
+    private static final Logger log = Logger.getLogger(AuthenticationSessionManager.class);
+
+    private final KeycloakSession session;
+
+    public AuthenticationSessionManager(KeycloakSession session) {
+        this.session = session;
+    }
+
+
+    public AuthenticationSessionModel createAuthenticationSession(RealmModel realm, ClientModel client, boolean browserCookie) {
+        AuthenticationSessionModel authSession = session.authenticationSessions().createAuthenticationSession(realm, client);
+
+        if (browserCookie) {
+            setAuthSessionCookie(authSession.getId(), realm);
+        }
+
+        return authSession;
+    }
+
+
+    public String getCurrentAuthenticationSessionId(RealmModel realm) {
+        return getAuthSessionCookie();
+    }
+
+
+    public AuthenticationSessionModel getCurrentAuthenticationSession(RealmModel realm) {
+        String authSessionId = getAuthSessionCookie();
+        return authSessionId==null ? null : session.authenticationSessions().getAuthenticationSession(realm, authSessionId);
+    }
+
+
+    public void setAuthSessionCookie(String authSessionId, RealmModel realm) {
+        UriInfo uriInfo = session.getContext().getUri();
+        String cookiePath = AuthenticationManager.getRealmCookiePath(realm, uriInfo);
+
+        boolean sslRequired = realm.getSslRequired().isRequired(session.getContext().getConnection());
+        CookieHelper.addCookie(AUTH_SESSION_ID, authSessionId, cookiePath, null, null, -1, sslRequired, true);
+
+        // TODO trace with isTraceEnabled
+        log.infof("Set AUTH_SESSION_ID cookie with value %s", authSessionId);
+    }
+
+
+    public String getAuthSessionCookie() {
+        String cookieVal = CookieHelper.getCookieValue(AUTH_SESSION_ID);
+
+        if (log.isTraceEnabled()) {
+            if (cookieVal != null) {
+                log.tracef("Found AUTH_SESSION_ID cookie with value %s", cookieVal);
+            } else {
+                log.tracef("Not found AUTH_SESSION_ID cookie");
+            }
+        }
+
+        return cookieVal;
+    }
+
+
+    public void removeAuthenticationSession(RealmModel realm, AuthenticationSessionModel authSession, boolean expireRestartCookie) {
+        log.infof("Removing authSession '%s'. Expire restart cookie: %b", authSession.getId(), expireRestartCookie);
+        session.authenticationSessions().removeAuthenticationSession(realm, authSession);
+
+        // expire restart cookie
+        if (expireRestartCookie) {
+            ClientConnection clientConnection = session.getContext().getConnection();
+            UriInfo uriInfo = session.getContext().getUri();
+            RestartLoginCookie.expireRestartCookie(realm, clientConnection, uriInfo);
+        }
+    }
+
+
+    // Check to see if we already have authenticationSession with same ID
+    public UserSessionModel getUserSession(AuthenticationSessionModel authSession) {
+        return session.sessions().getUserSession(authSession.getRealm(), authSession.getId());
+    }
+
+}
diff --git a/services/src/main/java/org/keycloak/services/messages/Messages.java b/services/src/main/java/org/keycloak/services/messages/Messages.java
index d9d3c4e..295f07b 100755
--- a/services/src/main/java/org/keycloak/services/messages/Messages.java
+++ b/services/src/main/java/org/keycloak/services/messages/Messages.java
@@ -33,6 +33,8 @@ public class Messages {
 
     public static final String EXPIRED_CODE = "expiredCodeMessage";
 
+    public static final String EXPIRED_ACTION = "expiredActionMessage";
+
     public static final String MISSING_FIRST_NAME = "missingFirstNameMessage";
 
     public static final String MISSING_LAST_NAME = "missingLastNameMessage";
@@ -197,5 +199,10 @@ public class Messages {
     public static final String FAILED_LOGOUT = "failedLogout";
 
     public static final String CONSENT_DENIED="consentDenied";
+
     public static final String ALREADY_LOGGED_IN="alreadyLoggedIn";
+
+    public static final String DIFFERENT_USER_AUTHENTICATED = "differentUserAuthenticated";
+
+    public static final String BROKER_LINKING_SESSION_EXPIRED = "brokerLinkingSessionExpired";
 }
diff --git a/services/src/main/java/org/keycloak/services/resources/AccountService.java b/services/src/main/java/org/keycloak/services/resources/AccountService.java
index 1894686..9dd9c4b 100755
--- a/services/src/main/java/org/keycloak/services/resources/AccountService.java
+++ b/services/src/main/java/org/keycloak/services/resources/AccountService.java
@@ -56,6 +56,7 @@ import org.keycloak.services.managers.UserSessionManager;
 import org.keycloak.services.messages.Messages;
 import org.keycloak.services.util.ResolveRelative;
 import org.keycloak.services.validation.Validation;
+import org.keycloak.sessions.AuthenticationSessionModel;
 import org.keycloak.storage.ReadOnlyException;
 import org.keycloak.util.JsonSerialization;
 
@@ -205,14 +206,18 @@ public class AccountService extends AbstractSecuredLocalService {
 
             setReferrerOnPage();
 
-            String forwardedError = auth.getClientSession().getNote(ACCOUNT_MGMT_FORWARDED_ERROR_NOTE);
-            if (forwardedError != null) {
-                try {
-                    FormMessage errorMessage = JsonSerialization.readValue(forwardedError, FormMessage.class);
-                    account.setError(errorMessage.getMessage(), errorMessage.getParameters());
-                    auth.getClientSession().removeNote(ACCOUNT_MGMT_FORWARDED_ERROR_NOTE);
-                } catch (IOException ioe) {
-                    throw new RuntimeException(ioe);
+            UserSessionModel userSession = auth.getClientSession().getUserSession();
+            AuthenticationSessionModel authSession = session.authenticationSessions().getAuthenticationSession(realm, userSession.getId());
+            if (authSession != null) {
+                String forwardedError = authSession.getAuthNote(ACCOUNT_MGMT_FORWARDED_ERROR_NOTE);
+                if (forwardedError != null) {
+                    try {
+                        FormMessage errorMessage = JsonSerialization.readValue(forwardedError, FormMessage.class);
+                        account.setError(errorMessage.getMessage(), errorMessage.getParameters());
+                        authSession.removeAuthNote(ACCOUNT_MGMT_FORWARDED_ERROR_NOTE);
+                    } catch (IOException ioe) {
+                        throw new RuntimeException(ioe);
+                    }
                 }
             }
 
@@ -766,7 +771,7 @@ public class AccountService extends AbstractSecuredLocalService {
                 try {
                     String nonce = UUID.randomUUID().toString();
                     MessageDigest md = MessageDigest.getInstance("SHA-256");
-                    String input = nonce + auth.getSession().getId() +  auth.getClientSession().getId() + providerId;
+                    String input = nonce + auth.getSession().getId() +  client.getClientId() + providerId;
                     byte[] check = md.digest(input.getBytes(StandardCharsets.UTF_8));
                     String hash = Base64Url.encode(check);
                     URI linkUrl = Urls.identityProviderLinkRequest(this.uriInfo.getBaseUri(), providerId, realm.getName());
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java b/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java
index c04b99e..fe6fe0f 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java
@@ -45,6 +45,7 @@ import org.keycloak.models.UserCredentialModel;
 import org.keycloak.models.UserLoginFailureModel;
 import org.keycloak.models.UserModel;
 import org.keycloak.models.UserSessionModel;
+import org.keycloak.models.utils.KeycloakModelUtils;
 import org.keycloak.models.utils.ModelToRepresentation;
 import org.keycloak.protocol.oidc.OIDCLoginProtocol;
 import org.keycloak.provider.ProviderFactory;
@@ -332,7 +333,8 @@ public class UsersResource {
         }
         EventBuilder event = new EventBuilder(realm, session, clientConnection);
 
-        UserSessionModel userSession = session.sessions().createUserSession(realm, user, user.getUsername(), clientConnection.getRemoteAddr(), "impersonate", false, null, null);
+        String sessionId = KeycloakModelUtils.generateId();
+        UserSessionModel userSession = session.sessions().createUserSession(sessionId, realm, user, user.getUsername(), clientConnection.getRemoteAddr(), "impersonate", false, null, null);
         AuthenticationManager.createLoginCookie(session, realm, userSession.getUser(), userSession, uriInfo, clientConnection);
         URI redirect = AccountService.accountServiceApplicationPage(uriInfo).build(realm.getName());
         Map<String, Object> result = new HashMap<>();
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 7492285..ded189d 100755
--- a/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java
+++ b/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java
@@ -16,977 +16,1127 @@
  */
 package org.keycloak.services.resources;
 
+import org.jboss.logging.Logger;
+import org.jboss.resteasy.annotations.cache.NoCache;
+import org.jboss.resteasy.spi.HttpRequest;
+import org.jboss.resteasy.spi.ResteasyProviderFactory;
+import org.keycloak.authentication.AuthenticationProcessor;
+import org.keycloak.authentication.authenticators.broker.AbstractIdpAuthenticator;
+import org.keycloak.authentication.authenticators.broker.util.PostBrokerLoginConstants;
+import org.keycloak.authentication.authenticators.broker.util.SerializedBrokeredIdentityContext;
+import org.keycloak.broker.provider.AuthenticationRequest;
+import org.keycloak.broker.provider.BrokeredIdentityContext;
 import org.keycloak.broker.provider.IdentityBrokerException;
 import org.keycloak.broker.provider.IdentityProvider;
 import org.keycloak.broker.provider.IdentityProviderFactory;
+import org.keycloak.broker.provider.IdentityProviderMapper;
+import org.keycloak.broker.saml.SAMLEndpoint;
 import org.keycloak.broker.social.SocialIdentityProvider;
+import org.keycloak.common.ClientConnection;
+import org.keycloak.common.util.Base64Url;
+import org.keycloak.common.util.ObjectUtil;
+import org.keycloak.common.util.Time;
+import org.keycloak.common.util.UriUtils;
+import org.keycloak.events.Details;
+import org.keycloak.events.Errors;
+import org.keycloak.events.EventBuilder;
+import org.keycloak.events.EventType;
+import org.keycloak.forms.login.LoginFormsProvider;
+import org.keycloak.models.AccountRoles;
+import org.keycloak.models.AuthenticatedClientSessionModel;
+import org.keycloak.models.AuthenticationFlowModel;
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.Constants;
+import org.keycloak.models.FederatedIdentityModel;
+import org.keycloak.models.IdentityProviderMapperModel;
 import org.keycloak.models.IdentityProviderModel;
 import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakSessionFactory;
 import org.keycloak.models.RealmModel;
+import org.keycloak.models.RoleModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.models.UserSessionModel;
+import org.keycloak.models.utils.FormMessage;
+import org.keycloak.protocol.oidc.OIDCLoginProtocol;
+import org.keycloak.protocol.oidc.TokenManager;
+import org.keycloak.protocol.oidc.utils.RedirectUtils;
+import org.keycloak.protocol.saml.SamlProtocol;
+import org.keycloak.protocol.saml.SamlService;
 import org.keycloak.provider.ProviderFactory;
+import org.keycloak.representations.AccessToken;
+import org.keycloak.services.ErrorPage;
+import org.keycloak.services.ErrorPageException;
+import org.keycloak.services.ErrorResponse;
+import org.keycloak.services.ServicesLogger;
+import org.keycloak.services.Urls;
+import org.keycloak.services.managers.AppAuthManager;
+import org.keycloak.services.managers.AuthenticationManager;
+import org.keycloak.services.managers.AuthenticationSessionManager;
+import org.keycloak.services.managers.BruteForceProtector;
+import org.keycloak.services.managers.ClientSessionCode;
+import org.keycloak.services.messages.Messages;
+import org.keycloak.services.util.CacheControlUtil;
+import org.keycloak.services.validation.Validation;
+import org.keycloak.sessions.AuthenticationSessionModel;
+import org.keycloak.util.JsonSerialization;
 
+import java.io.IOException;
+import java.net.URI;
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+import java.util.UUID;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.OPTIONS;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriBuilder;
+import javax.ws.rs.core.UriInfo;
 
 /**
  * <p></p>
  *
  * @author Pedro Igor
  */
-public class IdentityBrokerService {
-//public class IdentityBrokerService implements IdentityProvider.AuthenticationCallback {
-//
-//    private static final Logger logger = Logger.getLogger(IdentityBrokerService.class);
-//
-//    private final RealmModel realmModel;
-//
-//    @Context
-//    private UriInfo uriInfo;
-//
-//    @Context
-//    private KeycloakSession session;
-//
-//    @Context
-//    private ClientConnection clientConnection;
-//
-//    @Context
-//    private HttpRequest request;
-//
-//    @Context
-//    private HttpHeaders headers;
-//
-//    private EventBuilder event;
-//
-//
-//    public IdentityBrokerService(RealmModel realmModel) {
-//        if (realmModel == null) {
-//            throw new IllegalArgumentException("Realm can not be null.");
-//        }
-//        this.realmModel = realmModel;
-//    }
-//
-//    public void init() {
-//        this.event = new EventBuilder(realmModel, session, clientConnection).event(EventType.IDENTITY_PROVIDER_LOGIN);
-//    }
-//
-//    private void checkRealm() {
-//        if (!realmModel.isEnabled()) {
-//            event.error(Errors.REALM_DISABLED);
-//            throw new ErrorPageException(session, Messages.REALM_NOT_ENABLED);
-//        }
-//    }
-//
-//    private ClientModel checkClient(String clientId) {
-//        if (clientId == null) {
-//            event.error(Errors.INVALID_REQUEST);
-//            throw new ErrorPageException(session, Messages.MISSING_PARAMETER, OIDCLoginProtocol.CLIENT_ID_PARAM);
-//        }
-//
-//        event.client(clientId);
-//
-//        ClientModel client = realmModel.getClientByClientId(clientId);
-//        if (client == null) {
-//            event.error(Errors.CLIENT_NOT_FOUND);
-//            throw new ErrorPageException(session, Messages.INVALID_REQUEST);
-//        }
-//
-//        if (!client.isEnabled()) {
-//            event.error(Errors.CLIENT_DISABLED);
-//            throw new ErrorPageException(session, Messages.INVALID_REQUEST);
-//        }
-//        return client;
-//
-//    }
-//
-//    /**
-//     * Closes off CORS preflight requests for account linking
-//     *
-//     * @param providerId
-//     * @return
-//     */
-//    @OPTIONS
-//    @Path("/{provider_id}/link")
-//    public Response clientIntiatedAccountLinkingPreflight(@PathParam("provider_id") String providerId) {
-//        return Response.status(403).build(); // don't allow preflight
-//    }
-//
-//
-//    @GET
-//    @NoCache
-//    @Path("/{provider_id}/link")
-//    public Response clientInitiatedAccountLinking(@PathParam("provider_id") String providerId,
-//                                                  @QueryParam("redirect_uri") String redirectUri,
-//                                                  @QueryParam("client_id") String clientId,
-//                                                  @QueryParam("nonce") String nonce,
-//                                                  @QueryParam("hash") String hash
-//    ) {
-//        this.event.event(EventType.CLIENT_INITIATED_ACCOUNT_LINKING);
-//        checkRealm();
-//        ClientModel client = checkClient(clientId);
-//        AuthenticationManager authenticationManager = new AuthenticationManager();
-//        redirectUri = RedirectUtils.verifyRedirectUri(uriInfo, redirectUri, realmModel, client);
-//        if (redirectUri == null) {
-//            event.error(Errors.INVALID_REDIRECT_URI);
-//            throw new ErrorPageException(session, Messages.INVALID_REQUEST);
-//        }
-//
-//        if (nonce == null || hash == null) {
-//            event.error(Errors.INVALID_REDIRECT_URI);
-//            throw new ErrorPageException(session, Messages.INVALID_REQUEST);
-//
-//        }
-//
-//        // only allow origins from client.  Not sure we need this as I don't believe cookies can be
-//        // sent if CORS preflight requests can't execute.
-//        String origin = headers.getRequestHeaders().getFirst("Origin");
-//        if (origin != null) {
-//            String redirectOrigin = UriUtils.getOrigin(redirectUri);
-//            if (!redirectOrigin.equals(origin)) {
-//                event.error(Errors.ILLEGAL_ORIGIN);
-//                throw new ErrorPageException(session, Messages.INVALID_REQUEST);
-//
-//            }
-//        }
-//
-//        AuthResult cookieResult = authenticationManager.authenticateIdentityCookie(session, realmModel, true);
-//        String errorParam = "link_error";
-//        if (cookieResult == null) {
-//            event.error(Errors.NOT_LOGGED_IN);
-//            UriBuilder builder = UriBuilder.fromUri(redirectUri)
-//                    .queryParam(errorParam, Errors.NOT_LOGGED_IN)
-//                    .queryParam("nonce", nonce);
-//
-//            return Response.status(302).location(builder.build()).build();
-//        }
-//
-//
-//
-//        ClientLoginSessionModel clientSession = null;
-//        for (ClientLoginSessionModel cs : cookieResult.getSession().getAuthenticatedClientSessions().values()) {
-//            if (cs.getClient().getClientId().equals(clientId)) {
-//                byte[] decoded = Base64Url.decode(hash);
-//                MessageDigest md = null;
-//                try {
-//                    md = MessageDigest.getInstance("SHA-256");
-//                } catch (NoSuchAlgorithmException e) {
-//                    throw new ErrorPageException(session, Messages.UNEXPECTED_ERROR_HANDLING_REQUEST);
-//                }
-//                String input = nonce + cookieResult.getSession().getId() + cs.getId() + providerId;
-//                byte[] check = md.digest(input.getBytes(StandardCharsets.UTF_8));
-//                if (MessageDigest.isEqual(decoded, check)) {
-//                    clientSession = cs;
-//                    break;
-//                }
-//            }
-//        }
-//        if (clientSession == null) {
-//            event.error(Errors.INVALID_TOKEN);
-//            throw new ErrorPageException(session, Messages.INVALID_REQUEST);
-//        }
-//
-//
-//
-//        ClientModel accountService = this.realmModel.getClientByClientId(ACCOUNT_MANAGEMENT_CLIENT_ID);
-//        if (!accountService.getId().equals(client.getId())) {
-//            RoleModel manageAccountRole = accountService.getRole(MANAGE_ACCOUNT);
-//
-//            if (!clientSession.getRoles().contains(manageAccountRole.getId())) {
-//                RoleModel linkRole = accountService.getRole(MANAGE_ACCOUNT_LINKS);
-//                if (!clientSession.getRoles().contains(linkRole.getId())) {
-//                    event.error(Errors.NOT_ALLOWED);
-//                    UriBuilder builder = UriBuilder.fromUri(redirectUri)
-//                            .queryParam(errorParam, Errors.NOT_ALLOWED)
-//                            .queryParam("nonce", nonce);
-//                    return Response.status(302).location(builder.build()).build();
-//                }
-//            }
-//        }
-//
-//
-//        IdentityProviderModel identityProviderModel = realmModel.getIdentityProviderByAlias(providerId);
-//        if (identityProviderModel == null) {
-//            event.error(Errors.UNKNOWN_IDENTITY_PROVIDER);
-//            UriBuilder builder = UriBuilder.fromUri(redirectUri)
-//                    .queryParam(errorParam, Errors.UNKNOWN_IDENTITY_PROVIDER)
-//                    .queryParam("nonce", nonce);
-//            return Response.status(302).location(builder.build()).build();
-//
-//        }
-//
-//
-//        // TODO: Create AuthenticationSessionModel and Login cookie and set the state inside. See my notes document
-//        ClientSessionCode clientSessionCode = new ClientSessionCode(session, realmModel, clientSession);
-//        clientSessionCode.setAction(ClientSessionModel.Action.AUTHENTICATE.name());
-//        clientSessionCode.getCode();
-//        clientSession.setRedirectUri(redirectUri);
-//        clientSession.setNote(OIDCLoginProtocol.STATE_PARAM, UUID.randomUUID().toString());
-//
-//        event.success();
-//
-//
-//        try {
-//            IdentityProvider identityProvider = getIdentityProvider(session, realmModel, providerId);
-//            Response response = identityProvider.performLogin(createAuthenticationRequest(providerId, clientSessionCode));
-//
-//            if (response != null) {
-//                if (isDebugEnabled()) {
-//                    logger.debugf("Identity provider [%s] is going to send a request [%s].", identityProvider, response);
-//                }
-//                return response;
-//            }
-//        } catch (IdentityBrokerException e) {
-//            return redirectToErrorPage(Messages.COULD_NOT_SEND_AUTHENTICATION_REQUEST, e, providerId);
-//        } catch (Exception e) {
-//            return redirectToErrorPage(Messages.UNEXPECTED_ERROR_HANDLING_REQUEST, e, providerId);
-//        }
-//
-//        return redirectToErrorPage(Messages.COULD_NOT_PROCEED_WITH_AUTHENTICATION_REQUEST);
-//
-//    }
-//
-//
-//    @POST
-//    @Path("/{provider_id}/login")
-//    public Response performPostLogin(@PathParam("provider_id") String providerId, @QueryParam("code") String code) {
-//        return performLogin(providerId, code);
-//    }
-//
-//    @GET
-//    @NoCache
-//    @Path("/{provider_id}/login")
-//    public Response performLogin(@PathParam("provider_id") String providerId, @QueryParam("code") String code) {
-//        this.event.detail(Details.IDENTITY_PROVIDER, providerId);
-//
-//        if (isDebugEnabled()) {
-//            logger.debugf("Sending authentication request to identity provider [%s].", providerId);
-//        }
-//
-//        try {
-//            ParsedCodeContext parsedCode = parseClientSessionCode(code);
-//            if (parsedCode.response != null) {
-//                return parsedCode.response;
-//            }
-//
-//            ClientSessionCode clientSessionCode = parsedCode.clientSessionCode;
-//            IdentityProviderModel identityProviderModel = realmModel.getIdentityProviderByAlias(providerId);
-//            if (identityProviderModel == null) {
-//                throw new IdentityBrokerException("Identity Provider [" + providerId + "] not found.");
-//            }
-//            if (identityProviderModel.isLinkOnly()) {
-//                throw new IdentityBrokerException("Identity Provider [" + providerId + "] is not allowed to perform a login.");
-//
-//            }
-//            IdentityProviderFactory providerFactory = getIdentityProviderFactory(session, identityProviderModel);
-//
-//            IdentityProvider identityProvider = providerFactory.create(session, identityProviderModel);
-//
-//            Response response = identityProvider.performLogin(createAuthenticationRequest(providerId, clientSessionCode));
-//
-//            if (response != null) {
-//                if (isDebugEnabled()) {
-//                    logger.debugf("Identity provider [%s] is going to send a request [%s].", identityProvider, response);
-//                }
-//                return response;
-//            }
-//        } catch (IdentityBrokerException e) {
-//            return redirectToErrorPage(Messages.COULD_NOT_SEND_AUTHENTICATION_REQUEST, e, providerId);
-//        } catch (Exception e) {
-//            return redirectToErrorPage(Messages.UNEXPECTED_ERROR_HANDLING_REQUEST, e, providerId);
-//        }
-//
-//        return redirectToErrorPage(Messages.COULD_NOT_PROCEED_WITH_AUTHENTICATION_REQUEST);
-//    }
-//
-//    @Path("{provider_id}/endpoint")
-//    public Object getEndpoint(@PathParam("provider_id") String providerId) {
-//        IdentityProvider identityProvider = getIdentityProvider(session, realmModel, providerId);
-//        Object callback = identityProvider.callback(realmModel, this, event);
-//        ResteasyProviderFactory.getInstance().injectProperties(callback);
-//        //resourceContext.initResource(brokerService);
-//        return callback;
-//
-//
-//    }
-//
-//    @Path("{provider_id}/token")
-//    @OPTIONS
-//    public Response retrieveTokenPreflight() {
-//        return Cors.add(this.request, Response.ok()).auth().preflight().build();
-//    }
-//
-//    @GET
-//    @NoCache
-//    @Path("{provider_id}/token")
-//    public Response retrieveToken(@PathParam("provider_id") String providerId) {
-//        return getToken(providerId, false);
-//    }
-//
-//    private boolean canReadBrokerToken(AccessToken token) {
-//        Map<String, AccessToken.Access> resourceAccess = token.getResourceAccess();
-//        AccessToken.Access brokerRoles = resourceAccess == null ? null : resourceAccess.get(Constants.BROKER_SERVICE_CLIENT_ID);
-//        return brokerRoles != null && brokerRoles.isUserInRole(Constants.READ_TOKEN_ROLE);
-//    }
-//
-//    private Response getToken(String providerId, boolean forceRetrieval) {
-//        this.event.event(EventType.IDENTITY_PROVIDER_RETRIEVE_TOKEN);
-//
-//        try {
-//            AppAuthManager authManager = new AppAuthManager();
-//            AuthResult authResult = authManager.authenticateBearerToken(this.session, this.realmModel, this.uriInfo, this.clientConnection, this.request.getHttpHeaders());
-//
-//            if (authResult != null) {
-//                AccessToken token = authResult.getToken();
-//                String[] audience = token.getAudience();
-//                ClientModel clientModel = this.realmModel.getClientByClientId(audience[0]);
-//
-//                if (clientModel == null) {
-//                    return badRequest("Invalid client.");
-//                }
-//
-//                session.getContext().setClient(clientModel);
-//
-//                ClientModel brokerClient = realmModel.getClientByClientId(Constants.BROKER_SERVICE_CLIENT_ID);
-//                if (brokerClient == null) {
-//                    return corsResponse(forbidden("Realm has not migrated to support the broker token exchange service"), clientModel);
-//
-//                }
-//                if (!canReadBrokerToken(token)) {
-//                    return corsResponse(forbidden("Client [" + clientModel.getClientId() + "] not authorized to retrieve tokens from identity provider [" + providerId + "]."), clientModel);
-//
-//                }
-//
-//                IdentityProvider identityProvider = getIdentityProvider(session, realmModel, providerId);
-//                IdentityProviderModel identityProviderConfig = getIdentityProviderConfig(providerId);
-//
-//                if (identityProviderConfig.isStoreToken()) {
-//                    FederatedIdentityModel identity = this.session.users().getFederatedIdentity(authResult.getUser(), providerId, this.realmModel);
-//
-//                    if (identity == null) {
-//                        return corsResponse(badRequest("User [" + authResult.getUser().getId() + "] is not associated with identity provider [" + providerId + "]."), clientModel);
-//                    }
-//
-//                    this.event.success();
-//
-//                    return corsResponse(identityProvider.retrieveToken(session, identity), clientModel);
-//                }
-//
-//                return corsResponse(badRequest("Identity Provider [" + providerId + "] does not support this operation."), clientModel);
-//            }
-//
-//            return badRequest("Invalid token.");
-//        } catch (IdentityBrokerException e) {
-//            return redirectToErrorPage(Messages.COULD_NOT_OBTAIN_TOKEN, e, providerId);
-//        }  catch (Exception e) {
-//            return redirectToErrorPage(Messages.UNEXPECTED_ERROR_RETRIEVING_TOKEN, e, providerId);
-//        }
-//    }
-//
-//    public Response authenticated(BrokeredIdentityContext context) {
-//        IdentityProviderModel identityProviderConfig = context.getIdpConfig();
-//
-//        final ParsedCodeContext parsedCode;
-//        if (context.getContextData().get(SAMLEndpoint.SAML_IDP_INITIATED_CLIENT_ID) != null) {
-//            parsedCode = samlIdpInitiatedSSO((String) context.getContextData().get(SAMLEndpoint.SAML_IDP_INITIATED_CLIENT_ID));
-//        } else {
-//            parsedCode = parseClientSessionCode(context.getCode());
-//        }
-//        if (parsedCode.response != null) {
-//            return parsedCode.response;
-//        }
-//        ClientSessionCode<LoginSessionModel> clientCode = parsedCode.clientSessionCode;
-//
-//        String providerId = identityProviderConfig.getAlias();
-//        if (!identityProviderConfig.isStoreToken()) {
-//            if (isDebugEnabled()) {
-//                logger.debugf("Token will not be stored for identity provider [%s].", providerId);
-//            }
-//            context.setToken(null);
-//        }
-//
-//        AuthenticationSessionModel authSession = clientCode.getClientSession();
-//        context.setAuthenticationSession(authenticationSession);
-//
-//        session.getContext().setClient(authenticationSession.getClient());
-//
-//        context.getIdp().preprocessFederatedIdentity(session, realmModel, context);
-//        Set<IdentityProviderMapperModel> mappers = realmModel.getIdentityProviderMappersByAlias(context.getIdpConfig().getAlias());
-//        if (mappers != null) {
-//            KeycloakSessionFactory sessionFactory = session.getKeycloakSessionFactory();
-//            for (IdentityProviderMapperModel mapper : mappers) {
-//                IdentityProviderMapper target = (IdentityProviderMapper)sessionFactory.getProviderFactory(IdentityProviderMapper.class, mapper.getIdentityProviderMapper());
-//                target.preprocessFederatedIdentity(session, realmModel, mapper, context);
-//            }
-//        }
-//
-//        FederatedIdentityModel federatedIdentityModel = new FederatedIdentityModel(providerId, context.getId(),
-//                context.getUsername(), context.getToken());
-//
-//        this.event.event(EventType.IDENTITY_PROVIDER_LOGIN)
-//                .detail(Details.REDIRECT_URI, authenticationSession.getRedirectUri())
-//                .detail(Details.IDENTITY_PROVIDER_USERNAME, context.getUsername());
-//
-//        UserModel federatedUser = this.session.users().getUserByFederatedIdentity(federatedIdentityModel, this.realmModel);
-//
-//        // Check if federatedUser is already authenticated (this means linking social into existing federatedUser account)
-//        if (authenticationSession.getUserSession() != null) {
-//            return performAccountLinking(clientSession, context, federatedIdentityModel, federatedUser);
-//        }
-//
-//        if (federatedUser == null) {
-//
-//            logger.debugf("Federated user not found for provider '%s' and broker username '%s' . Redirecting to flow for firstBrokerLogin", providerId, context.getUsername());
-//
-//            String username = context.getModelUsername();
-//            if (username == null) {
-//                if (this.realmModel.isRegistrationEmailAsUsername() && !Validation.isBlank(context.getEmail())) {
-//                    username = context.getEmail();
-//                } else if (context.getUsername() == null) {
-//                    username = context.getIdpConfig().getAlias() + "." + context.getId();
-//                } else {
-//                    username = context.getUsername();
-//                }
-//            }
-//            username = username.trim();
-//            context.setModelUsername(username);
-//
-//            clientSession.setTimestamp(Time.currentTime());
-//
-//            SerializedBrokeredIdentityContext ctx = SerializedBrokeredIdentityContext.serialize(context);
-//            ctx.saveToClientSession(clientSession, AbstractIdpAuthenticator.BROKERED_CONTEXT_NOTE);
-//
-//            URI redirect = LoginActionsService.firstBrokerLoginProcessor(uriInfo)
-//                    .queryParam(OAuth2Constants.CODE, clientCode.getCode())
-//                    .build(realmModel.getName());
-//            return Response.status(302).location(redirect).build();
-//
-//        } else {
-//            Response response = validateUser(federatedUser, realmModel);
-//            if (response != null) {
-//                return response;
-//            }
-//
-//            updateFederatedIdentity(context, federatedUser);
-//            clientSession.setAuthenticatedUser(federatedUser);
-//
-//            return finishOrRedirectToPostBrokerLogin(clientSession, context, false, parsedCode.clientSessionCode);
-//        }
-//    }
-//
-//    public Response validateUser(UserModel user, RealmModel realm) {
-//        if (!user.isEnabled()) {
-//            event.error(Errors.USER_DISABLED);
-//            return ErrorPage.error(session, 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 null;
-//    }
-//
-//    // Callback from LoginActionsService after first login with broker was done and Keycloak account is successfully linked/created
-//    @GET
-//    @NoCache
-//    @Path("/after-first-broker-login")
-//    public Response afterFirstBrokerLogin(@QueryParam("code") String code) {
-//        ParsedCodeContext parsedCode = parseClientSessionCode(code);
-//        if (parsedCode.response != null) {
-//            return parsedCode.response;
-//        }
-//        return afterFirstBrokerLogin(parsedCode.clientSessionCode);
-//    }
-//
-//    private Response afterFirstBrokerLogin(ClientSessionCode clientSessionCode) {
-//        ClientSessionModel clientSession = clientSessionCode.getClientSession();
-//
-//        try {
-//            this.event.detail(Details.CODE_ID, clientSession.getId())
-//                    .removeDetail("auth_method");
-//
-//            SerializedBrokeredIdentityContext serializedCtx = SerializedBrokeredIdentityContext.readFromClientSession(clientSession, AbstractIdpAuthenticator.BROKERED_CONTEXT_NOTE);
-//            if (serializedCtx == null) {
-//                throw new IdentityBrokerException("Not found serialized context in clientSession");
-//            }
-//            BrokeredIdentityContext context = serializedCtx.deserialize(session, clientSession);
-//            String providerId = context.getIdpConfig().getAlias();
-//
-//            event.detail(Details.IDENTITY_PROVIDER, providerId);
-//            event.detail(Details.IDENTITY_PROVIDER_USERNAME, context.getUsername());
-//
-//            // firstBrokerLogin workflow finished. Removing note now
-//            clientSession.removeNote(AbstractIdpAuthenticator.BROKERED_CONTEXT_NOTE);
-//
-//            UserModel federatedUser = clientSession.getAuthenticatedUser();
-//            if (federatedUser == null) {
-//                throw new IdentityBrokerException("Couldn't found authenticated federatedUser in clientSession");
-//            }
-//
-//            event.user(federatedUser);
-//            event.detail(Details.USERNAME, federatedUser.getUsername());
-//
-//            if (context.getIdpConfig().isAddReadTokenRoleOnCreate()) {
-//                ClientModel brokerClient = realmModel.getClientByClientId(Constants.BROKER_SERVICE_CLIENT_ID);
-//                if (brokerClient == null) {
-//                    throw new IdentityBrokerException("Client 'broker' not available. Maybe realm has not migrated to support the broker token exchange service");
-//                }
-//                RoleModel readTokenRole = brokerClient.getRole(Constants.READ_TOKEN_ROLE);
-//                federatedUser.grantRole(readTokenRole);
-//            }
-//
-//            // Add federated identity link here
-//            FederatedIdentityModel federatedIdentityModel = new FederatedIdentityModel(context.getIdpConfig().getAlias(), context.getId(),
-//                    context.getUsername(), context.getToken());
-//            session.users().addFederatedIdentity(realmModel, federatedUser, federatedIdentityModel);
-//
-//
-//            String isRegisteredNewUser = clientSession.getNote(AbstractIdpAuthenticator.BROKER_REGISTERED_NEW_USER);
-//            if (Boolean.parseBoolean(isRegisteredNewUser)) {
-//
-//                logger.debugf("Registered new user '%s' after first login with identity provider '%s'. Identity provider username is '%s' . ", federatedUser.getUsername(), providerId, context.getUsername());
-//
-//                context.getIdp().importNewUser(session, realmModel, federatedUser, context);
-//                Set<IdentityProviderMapperModel> mappers = realmModel.getIdentityProviderMappersByAlias(providerId);
-//                if (mappers != null) {
-//                    KeycloakSessionFactory sessionFactory = session.getKeycloakSessionFactory();
-//                    for (IdentityProviderMapperModel mapper : mappers) {
-//                        IdentityProviderMapper target = (IdentityProviderMapper)sessionFactory.getProviderFactory(IdentityProviderMapper.class, mapper.getIdentityProviderMapper());
-//                        target.importNewUser(session, realmModel, federatedUser, mapper, context);
-//                    }
-//                }
-//
-//                if (context.getIdpConfig().isTrustEmail() && !Validation.isBlank(federatedUser.getEmail()) && !Boolean.parseBoolean(clientSession.getNote(AbstractIdpAuthenticator.UPDATE_PROFILE_EMAIL_CHANGED))) {
-//                    logger.debugf("Email verified automatically after registration of user '%s' through Identity provider '%s' ", federatedUser.getUsername(), context.getIdpConfig().getAlias());
-//                    federatedUser.setEmailVerified(true);
-//                }
-//
-//                event.event(EventType.REGISTER)
-//                        .detail(Details.REGISTER_METHOD, "broker")
-//                        .detail(Details.EMAIL, federatedUser.getEmail())
-//                        .success();
-//
-//            } else {
-//                logger.debugf("Linked existing keycloak user '%s' with identity provider '%s' . Identity provider username is '%s' .", federatedUser.getUsername(), providerId, context.getUsername());
-//
-//                event.event(EventType.FEDERATED_IDENTITY_LINK)
-//                        .success();
-//
-//                updateFederatedIdentity(context, federatedUser);
-//            }
-//
-//            return finishOrRedirectToPostBrokerLogin(clientSession, context, true, clientSessionCode);
-//
-//        }  catch (Exception e) {
-//            return redirectToErrorPage(Messages.IDENTITY_PROVIDER_UNEXPECTED_ERROR, e);
-//        }
-//    }
-//
-//
-//    private Response finishOrRedirectToPostBrokerLogin(ClientSessionModel clientSession, BrokeredIdentityContext context, boolean wasFirstBrokerLogin, ClientSessionCode clientSessionCode) {
-//        String postBrokerLoginFlowId = context.getIdpConfig().getPostBrokerLoginFlowId();
-//        if (postBrokerLoginFlowId == null) {
-//
-//            logger.debugf("Skip redirect to postBrokerLogin flow. PostBrokerLogin flow not set for identityProvider '%s'.", context.getIdpConfig().getAlias());
-//            return afterPostBrokerLoginFlowSuccess(clientSession, context, wasFirstBrokerLogin, clientSessionCode);
-//        } else {
-//
-//            logger.debugf("Redirect to postBrokerLogin flow after authentication with identityProvider '%s'.", context.getIdpConfig().getAlias());
-//
-//            clientSession.setTimestamp(Time.currentTime());
-//
-//            SerializedBrokeredIdentityContext ctx = SerializedBrokeredIdentityContext.serialize(context);
-//            ctx.saveToClientSession(clientSession, PostBrokerLoginConstants.PBL_BROKERED_IDENTITY_CONTEXT);
-//
-//            clientSession.setNote(PostBrokerLoginConstants.PBL_AFTER_FIRST_BROKER_LOGIN, String.valueOf(wasFirstBrokerLogin));
-//
-//            URI redirect = LoginActionsService.postBrokerLoginProcessor(uriInfo)
-//                    .queryParam(OAuth2Constants.CODE, clientSessionCode.getCode())
-//                    .build(realmModel.getName());
-//            return Response.status(302).location(redirect).build();
-//        }
-//    }
-//
-//
-//    // Callback from LoginActionsService after postBrokerLogin flow is finished
-//    @GET
-//    @NoCache
-//    @Path("/after-post-broker-login")
-//    public Response afterPostBrokerLoginFlow(@QueryParam("code") String code) {
-//        ParsedCodeContext parsedCode = parseClientSessionCode(code);
-//        if (parsedCode.response != null) {
-//            return parsedCode.response;
-//        }
-//        AuthenticationSessionModel authenticationSession = parsedCode.clientSessionCode.getClientSession();
-//
-//        try {
-//            SerializedBrokeredIdentityContext serializedCtx = SerializedBrokeredIdentityContext.readFromLoginSession(authenticationSession, PostBrokerLoginConstants.PBL_BROKERED_IDENTITY_CONTEXT);
-//            if (serializedCtx == null) {
-//                throw new IdentityBrokerException("Not found serialized context in clientSession. Note " + PostBrokerLoginConstants.PBL_BROKERED_IDENTITY_CONTEXT + " was null");
-//            }
-//            BrokeredIdentityContext context = serializedCtx.deserialize(session, authenticationSession);
-//
-//            String wasFirstBrokerLoginNote = authenticationSession.getNote(PostBrokerLoginConstants.PBL_AFTER_FIRST_BROKER_LOGIN);
-//            boolean wasFirstBrokerLogin = Boolean.parseBoolean(wasFirstBrokerLoginNote);
-//
-//            // Ensure the post-broker-login flow was successfully finished
-//            String authStateNoteKey = PostBrokerLoginConstants.PBL_AUTH_STATE_PREFIX + context.getIdpConfig().getAlias();
-//            String authState = authenticationSession.getNote(authStateNoteKey);
-//            if (!Boolean.parseBoolean(authState)) {
-//                throw new IdentityBrokerException("Invalid request. Not found the flag that post-broker-login flow was finished");
-//            }
-//
-//            // remove notes
-//            authenticationSession.removeNote(PostBrokerLoginConstants.PBL_BROKERED_IDENTITY_CONTEXT);
-//            authenticationSession.removeNote(PostBrokerLoginConstants.PBL_AFTER_FIRST_BROKER_LOGIN);
-//
-//            return afterPostBrokerLoginFlowSuccess(authenticationSession, context, wasFirstBrokerLogin, parsedCode.clientSessionCode);
-//        } catch (IdentityBrokerException e) {
-//            return redirectToErrorPage(Messages.IDENTITY_PROVIDER_UNEXPECTED_ERROR, e);
-//        }
-//    }
-//
-//    private Response afterPostBrokerLoginFlowSuccess(ClientSessionModel clientSession, BrokeredIdentityContext context, boolean wasFirstBrokerLogin, ClientSessionCode clientSessionCode) {
-//        String providerId = context.getIdpConfig().getAlias();
-//        UserModel federatedUser = clientSession.getAuthenticatedUser();
-//
-//        if (wasFirstBrokerLogin) {
-//
-//            String isDifferentBrowser = clientSession.getNote(AbstractIdpAuthenticator.IS_DIFFERENT_BROWSER);
-//            if (Boolean.parseBoolean(isDifferentBrowser)) {
-//                session.sessions().removeClientSession(realmModel, clientSession);
-//                return session.getProvider(LoginFormsProvider.class)
-//                        .setSuccess(Messages.IDENTITY_PROVIDER_LINK_SUCCESS, context.getIdpConfig().getAlias(), context.getUsername())
-//                        .createInfoPage();
-//            } else {
-//                return finishBrokerAuthentication(context, federatedUser, clientSession, providerId);
-//            }
-//
-//        } else {
-//
-//            boolean firstBrokerLoginInProgress = (clientSession.getNote(AbstractIdpAuthenticator.BROKERED_CONTEXT_NOTE) != null);
-//            if (firstBrokerLoginInProgress) {
-//                logger.debugf("Reauthenticated with broker '%s' when linking user '%s' with other broker", context.getIdpConfig().getAlias(), federatedUser.getUsername());
-//
-//                UserModel linkingUser = AbstractIdpAuthenticator.getExistingUser(session, realmModel, clientSession);
-//                if (!linkingUser.getId().equals(federatedUser.getId())) {
-//                    return redirectToErrorPage(Messages.IDENTITY_PROVIDER_DIFFERENT_USER_MESSAGE, federatedUser.getUsername(), linkingUser.getUsername());
-//                }
-//
-//                return afterFirstBrokerLogin(clientSessionCode);
-//            } else {
-//                return finishBrokerAuthentication(context, federatedUser, clientSession, providerId);
-//            }
-//        }
-//    }
-//
-//
-//    private Response finishBrokerAuthentication(BrokeredIdentityContext context, UserModel federatedUser, ClientSessionModel clientSession, String providerId) {
-//        UserSessionModel userSession = this.session.sessions()
-//                .createUserSession(this.realmModel, federatedUser, federatedUser.getUsername(), this.clientConnection.getRemoteAddr(), "broker", false, context.getBrokerSessionId(), context.getBrokerUserId());
-//
-//        this.event.user(federatedUser);
-//        this.event.session(userSession);
-//
-//        // TODO: This is supposed to be called after requiredActions are processed
-//        TokenManager.attachClientSession(userSession, clientSession);
-//        context.getIdp().attachUserSession(userSession, clientSession, context);
-//        userSession.setNote(Details.IDENTITY_PROVIDER, providerId);
-//        userSession.setNote(Details.IDENTITY_PROVIDER_USERNAME, context.getUsername());
-//
-//        if (isDebugEnabled()) {
-//            logger.debugf("Performing local authentication for user [%s].", federatedUser);
-//        }
-//
-//        return AuthenticationProcessor.redirectToRequiredActions(session, realmModel, clientSession, uriInfo);
-//    }
-//
-//
-//    @Override
-//    public Response cancelled(String code) {
-//        ParsedCodeContext parsedCode = parseClientSessionCode(code);
-//        if (parsedCode.response != null) {
-//            return parsedCode.response;
-//        }
-//        ClientSessionCode clientCode = parsedCode.clientSessionCode;
-//
-//        Response accountManagementFailedLinking = checkAccountManagementFailedLinking(clientCode.getClientSession(), Messages.CONSENT_DENIED);
-//        if (accountManagementFailedLinking != null) {
-//            return accountManagementFailedLinking;
-//        }
-//
-//        return browserAuthentication(clientCode.getClientSession(), null);
-//    }
-//
-//    @Override
-//    public Response error(String code, String message) {
-//        ParsedCodeContext parsedCode = parseClientSessionCode(code);
-//        if (parsedCode.response != null) {
-//            return parsedCode.response;
-//        }
-//        ClientSessionCode clientCode = parsedCode.clientSessionCode;
-//
-//        Response accountManagementFailedLinking = checkAccountManagementFailedLinking(clientCode.getClientSession(), message);
-//        if (accountManagementFailedLinking != null) {
-//            return accountManagementFailedLinking;
-//        }
-//
-//        return browserAuthentication(clientCode.getClientSession(), message);
-//    }
-//
-//    private Response performAccountLinking(ClientSessionModel clientSession, BrokeredIdentityContext context, FederatedIdentityModel newModel, UserModel federatedUser) {
-//        this.event.event(EventType.FEDERATED_IDENTITY_LINK);
-//
-//
-//
-//        UserModel authenticatedUser = clientSession.getUserSession().getUser();
-//
-//        if (federatedUser != null && !authenticatedUser.getId().equals(federatedUser.getId())) {
-//            return redirectToAccountErrorPage(clientSession, Messages.IDENTITY_PROVIDER_ALREADY_LINKED, context.getIdpConfig().getAlias());
-//        }
-//
-//        if (!authenticatedUser.hasRole(this.realmModel.getClientByClientId(ACCOUNT_MANAGEMENT_CLIENT_ID).getRole(MANAGE_ACCOUNT))) {
-//            return redirectToErrorPage(Messages.INSUFFICIENT_PERMISSION);
-//        }
-//
-//        if (!authenticatedUser.isEnabled()) {
-//            return redirectToAccountErrorPage(clientSession, Messages.ACCOUNT_DISABLED);
-//        }
-//
-//
-//
-//        if (federatedUser != null) {
-//            if (context.getIdpConfig().isStoreToken()) {
-//                FederatedIdentityModel oldModel = this.session.users().getFederatedIdentity(federatedUser, context.getIdpConfig().getAlias(), this.realmModel);
-//                if (!ObjectUtil.isEqualOrBothNull(context.getToken(), oldModel.getToken())) {
-//                    this.session.users().updateFederatedIdentity(this.realmModel, federatedUser, newModel);
-//                    if (isDebugEnabled()) {
-//                        logger.debugf("Identity [%s] update with response from identity provider [%s].", federatedUser, context.getIdpConfig().getAlias());
-//                    }
-//                }
-//            }
-//        } else {
-//            this.session.users().addFederatedIdentity(this.realmModel, authenticatedUser, newModel);
-//        }
-//        context.getIdp().attachUserSession(clientSession.getUserSession(), clientSession, context);
-//
-//
-//        if (isDebugEnabled()) {
-//            logger.debugf("Linking account [%s] from identity provider [%s] to user [%s].", newModel, context.getIdpConfig().getAlias(), authenticatedUser);
-//        }
-//
-//        this.event.user(authenticatedUser)
-//                .detail(Details.USERNAME, authenticatedUser.getUsername())
-//                .detail(Details.IDENTITY_PROVIDER, newModel.getIdentityProvider())
-//                .detail(Details.IDENTITY_PROVIDER_USERNAME, newModel.getUserName())
-//                .success();
-//
-//        // we do this to make sure that the parent IDP is logged out when this user session is complete.
-//
-//        clientSession.getUserSession().setNote(Details.IDENTITY_PROVIDER, context.getIdpConfig().getAlias());
-//        clientSession.getUserSession().setNote(Details.IDENTITY_PROVIDER_USERNAME, context.getUsername());
-//
-//        return Response.status(302).location(UriBuilder.fromUri(clientSession.getRedirectUri()).build()).build();
-//    }
-//
-//    private void updateFederatedIdentity(BrokeredIdentityContext context, UserModel federatedUser) {
-//        FederatedIdentityModel federatedIdentityModel = this.session.users().getFederatedIdentity(federatedUser, context.getIdpConfig().getAlias(), this.realmModel);
-//
-//        // Skip DB write if tokens are null or equal
-//        updateToken(context, federatedUser, federatedIdentityModel);
-//        context.getIdp().updateBrokeredUser(session, realmModel, federatedUser, context);
-//        Set<IdentityProviderMapperModel> mappers = realmModel.getIdentityProviderMappersByAlias(context.getIdpConfig().getAlias());
-//        if (mappers != null) {
-//            KeycloakSessionFactory sessionFactory = session.getKeycloakSessionFactory();
-//            for (IdentityProviderMapperModel mapper : mappers) {
-//                IdentityProviderMapper target = (IdentityProviderMapper)sessionFactory.getProviderFactory(IdentityProviderMapper.class, mapper.getIdentityProviderMapper());
-//                target.updateBrokeredUser(session, realmModel, federatedUser, mapper, context);
-//            }
-//        }
-//
-//    }
-//
-//    private void updateToken(BrokeredIdentityContext context, UserModel federatedUser, FederatedIdentityModel federatedIdentityModel) {
-//        if (context.getIdpConfig().isStoreToken() && !ObjectUtil.isEqualOrBothNull(context.getToken(), federatedIdentityModel.getToken())) {
-//            federatedIdentityModel.setToken(context.getToken());
-//
-//            this.session.users().updateFederatedIdentity(this.realmModel, federatedUser, federatedIdentityModel);
-//
-//            if (isDebugEnabled()) {
-//                logger.debugf("Identity [%s] update with response from identity provider [%s].", federatedUser, context.getIdpConfig().getAlias());
-//            }
-//        }
-//    }
-//
-//    private ParsedCodeContext parseClientSessionCode(String code) {
-//        ClientSessionCode<LoginSessionModel> clientCode = ClientSessionCode.parse(code, this.session, this.realmModel, AuthenticationSessionModel.class);
-//
-//        if (clientCode != null) {
-//            AuthenticationSessionModel authenticationSession = clientCode.getClientSession();
-//
-//            ClientModel client = authenticationSession.getClient();
-//
-//            if (client != null) {
-//
-//                logger.debugf("Got authorization code from client [%s].", client.getClientId());
-//                this.event.client(client);
-//                this.session.getContext().setClient(client);
-//
-//                if (!clientCode.isValid(AUTHENTICATE.name(), ClientSessionCode.ActionType.LOGIN)) {
-//                    logger.debugf("Authorization code is not valid. Client session ID: %s, Client session's action: %s", authenticationSession.getId(), authenticationSession.getAction());
-//
-//                    // Check if error happened during login or during linking from account management
-//                    Response accountManagementFailedLinking = checkAccountManagementFailedLinking(clientCode.getClientSession(), Messages.STALE_CODE_ACCOUNT);
-//                    Response staleCodeError = (accountManagementFailedLinking != null) ? accountManagementFailedLinking : redirectToErrorPage(Messages.STALE_CODE);
-//
-//
-//                    return ParsedCodeContext.response(staleCodeError);
-//                }
-//
-//                if (isDebugEnabled()) {
-//                    logger.debugf("Authorization code is valid.");
-//                }
-//
-//                return ParsedCodeContext.clientSessionCode(clientCode);
-//            }
-//        }
-//
-//        logger.debugf("Authorization code is not valid. Code: %s", code);
-//        Response staleCodeError = redirectToErrorPage(Messages.STALE_CODE);
-//        return ParsedCodeContext.response(staleCodeError);
-//    }
-//
-//    /**
-//     * If there is a client whose SAML IDP-initiated SSO URL name is set to the
-//     * given {@code clientUrlName}, creates a fresh client session for that
-//     * client and returns a {@link ParsedCodeContext} object with that session.
-//     * Otherwise returns "client not found" response.
-//     *
-//     * @param clientUrlName
-//     * @return see description
-//     */
-//    private ParsedCodeContext samlIdpInitiatedSSO(final String clientUrlName) {
-//        event.event(EventType.LOGIN);
-//        CacheControlUtil.noBackButtonCacheControlHeader();
-//        Optional<ClientModel> oClient = this.realmModel.getClients().stream()
-//          .filter(c -> Objects.equals(c.getAttribute(SamlProtocol.SAML_IDP_INITIATED_SSO_URL_NAME), clientUrlName))
-//          .findFirst();
-//
-//        if (! oClient.isPresent()) {
-//            event.error(Errors.CLIENT_NOT_FOUND);
-//            return ParsedCodeContext.response(redirectToErrorPage(Messages.CLIENT_NOT_FOUND));
-//        }
-//
-//        ClientSessionModel clientSession = SamlService.createClientSessionForIdpInitiatedSso(session, realmModel, oClient.get(), null);
-//
-//        return ParsedCodeContext.clientSessionCode(new ClientSessionCode(session, this.realmModel, clientSession));
-//    }
-//
-//    private Response checkAccountManagementFailedLinking(LoginSessionModel authenticationSession, String error, Object... parameters) {
-//        if (clientSession.getUserSession() != null && clientSession.getClient() != null && clientSession.getClient().getClientId().equals(ACCOUNT_MANAGEMENT_CLIENT_ID)) {
-//
-//            this.event.event(EventType.FEDERATED_IDENTITY_LINK);
-//            UserModel user = clientSession.getUserSession().getUser();
-//            this.event.user(user);
-//            this.event.detail(Details.USERNAME, user.getUsername());
-//
-//            return redirectToAccountErrorPage(clientSession, error, parameters);
-//        } else {
-//            return null;
-//        }
-//    }
-//
-//    private AuthenticationRequest createAuthenticationRequest(String providerId, ClientSessionCode<LoginSessionModel> clientSessionCode) {
-//        AuthenticationSessionModel authenticationSession = null;
-//        String relayState = null;
-//
-//        if (clientSessionCode != null) {
-//            authenticationSession = clientSessionCode.getClientSession();
-//            relayState = clientSessionCode.getCode();
-//        }
-//
-//        return new AuthenticationRequest(this.session, this.realmModel, clientSession, this.request, this.uriInfo, relayState, getRedirectUri(providerId));
-//    }
-//
-//    private String getRedirectUri(String providerId) {
-//        return Urls.identityProviderAuthnResponse(this.uriInfo.getBaseUri(), providerId, this.realmModel.getName()).toString();
-//    }
-//
-//    private Response redirectToErrorPage(String message, Object ... parameters) {
-//        return redirectToErrorPage(message, null, parameters);
-//    }
-//
-//    private Response redirectToErrorPage(String message, Throwable throwable, Object ... parameters) {
-//        if (message == null) {
-//            message = Messages.IDENTITY_PROVIDER_UNEXPECTED_ERROR;
-//        }
-//
-//        fireErrorEvent(message, throwable);
-//        return ErrorPage.error(this.session, message, parameters);
-//    }
-//
-//    private Response redirectToAccountErrorPage(ClientSessionModel clientSession, String message, Object ... parameters) {
-//        fireErrorEvent(message);
-//
-//        FormMessage errorMessage = new FormMessage(message, parameters);
-//        try {
-//            String serializedError = JsonSerialization.writeValueAsString(errorMessage);
-//            clientSession.setNote(AccountService.ACCOUNT_MGMT_FORWARDED_ERROR_NOTE, serializedError);
-//        } catch (IOException ioe) {
-//            throw new RuntimeException(ioe);
-//        }
-//
-//        return Response.status(302).location(UriBuilder.fromUri(clientSession.getRedirectUri()).build()).build();
-//    }
-//
-//    private Response redirectToLoginPage(Throwable t, ClientSessionCode clientCode) {
-//        String message = t.getMessage();
-//
-//        if (message == null) {
-//            message = Messages.IDENTITY_PROVIDER_UNEXPECTED_ERROR;
-//        }
-//
-//        fireErrorEvent(message);
-//        return browserAuthentication(clientCode.getClientSession(), message);
-//    }
-//
-//    protected Response browserAuthentication(ClientSessionModel clientSession, String errorMessage) {
-//        this.event.event(EventType.LOGIN);
-//        AuthenticationFlowModel flow = realmModel.getBrowserFlow();
-//        String flowId = flow.getId();
-//        AuthenticationProcessor processor = new AuthenticationProcessor();
-//        processor.setClientSession(clientSession)
-//                .setFlowPath(LoginActionsService.AUTHENTICATE_PATH)
-//                .setFlowId(flowId)
-//                .setBrowserFlow(true)
-//                .setConnection(clientConnection)
-//                .setEventBuilder(event)
-//                .setRealm(realmModel)
-//                .setSession(session)
-//                .setUriInfo(uriInfo)
-//                .setRequest(request);
-//        if (errorMessage != null) processor.setForwardedErrorMessage(new FormMessage(null, errorMessage));
-//
-//        try {
-//            CacheControlUtil.noBackButtonCacheControlHeader();
-//            return processor.authenticate();
-//        } catch (Exception e) {
-//            return processor.handleBrowserException(e);
-//        }
-//    }
-//
-//
-//    private Response badRequest(String message) {
-//        fireErrorEvent(message);
-//        return ErrorResponse.error(message, Status.BAD_REQUEST);
-//    }
-//
-//    private Response forbidden(String message) {
-//        fireErrorEvent(message);
-//        return ErrorResponse.error(message, Status.FORBIDDEN);
-//    }
+public class IdentityBrokerService implements IdentityProvider.AuthenticationCallback {
+
+    // Authentication session note, which references identity provider that is currently linked
+    private static final String LINKING_IDENTITY_PROVIDER = "LINKING_IDENTITY_PROVIDER";
+
+    private static final Logger logger = Logger.getLogger(IdentityBrokerService.class);
+
+    private final RealmModel realmModel;
+
+    @Context
+    private UriInfo uriInfo;
+
+    @Context
+    private KeycloakSession session;
+
+    @Context
+    private ClientConnection clientConnection;
+
+    @Context
+    private HttpRequest request;
+
+    @Context
+    private HttpHeaders headers;
+
+    private EventBuilder event;
+
+
+    public IdentityBrokerService(RealmModel realmModel) {
+        if (realmModel == null) {
+            throw new IllegalArgumentException("Realm can not be null.");
+        }
+        this.realmModel = realmModel;
+    }
+
+    public void init() {
+        this.event = new EventBuilder(realmModel, session, clientConnection).event(EventType.IDENTITY_PROVIDER_LOGIN);
+    }
+
+    private void checkRealm() {
+        if (!realmModel.isEnabled()) {
+            event.error(Errors.REALM_DISABLED);
+            throw new ErrorPageException(session, Messages.REALM_NOT_ENABLED);
+        }
+    }
+
+    private ClientModel checkClient(String clientId) {
+        if (clientId == null) {
+            event.error(Errors.INVALID_REQUEST);
+            throw new ErrorPageException(session, Messages.MISSING_PARAMETER, OIDCLoginProtocol.CLIENT_ID_PARAM);
+        }
+
+        event.client(clientId);
+
+        ClientModel client = realmModel.getClientByClientId(clientId);
+        if (client == null) {
+            event.error(Errors.CLIENT_NOT_FOUND);
+            throw new ErrorPageException(session, Messages.INVALID_REQUEST);
+        }
+
+        if (!client.isEnabled()) {
+            event.error(Errors.CLIENT_DISABLED);
+            throw new ErrorPageException(session, Messages.INVALID_REQUEST);
+        }
+        return client;
+
+    }
+
+    /**
+     * Closes off CORS preflight requests for account linking
+     *
+     * @param providerId
+     * @return
+     */
+    @OPTIONS
+    @Path("/{provider_id}/link")
+    public Response clientIntiatedAccountLinkingPreflight(@PathParam("provider_id") String providerId) {
+        return Response.status(403).build(); // don't allow preflight
+    }
+
+
+    @GET
+    @NoCache
+    @Path("/{provider_id}/link")
+    public Response clientInitiatedAccountLinking(@PathParam("provider_id") String providerId,
+                                                  @QueryParam("redirect_uri") String redirectUri,
+                                                  @QueryParam("client_id") String clientId,
+                                                  @QueryParam("nonce") String nonce,
+                                                  @QueryParam("hash") String hash
+    ) {
+        this.event.event(EventType.CLIENT_INITIATED_ACCOUNT_LINKING);
+        checkRealm();
+        ClientModel client = checkClient(clientId);
+        redirectUri = RedirectUtils.verifyRedirectUri(uriInfo, redirectUri, realmModel, client);
+        if (redirectUri == null) {
+            event.error(Errors.INVALID_REDIRECT_URI);
+            throw new ErrorPageException(session, Messages.INVALID_REQUEST);
+        }
+
+        if (nonce == null || hash == null) {
+            event.error(Errors.INVALID_REDIRECT_URI);
+            throw new ErrorPageException(session, Messages.INVALID_REQUEST);
+
+        }
+
+        // only allow origins from client.  Not sure we need this as I don't believe cookies can be
+        // sent if CORS preflight requests can't execute.
+        String origin = headers.getRequestHeaders().getFirst("Origin");
+        if (origin != null) {
+            String redirectOrigin = UriUtils.getOrigin(redirectUri);
+            if (!redirectOrigin.equals(origin)) {
+                event.error(Errors.ILLEGAL_ORIGIN);
+                throw new ErrorPageException(session, Messages.INVALID_REQUEST);
+
+            }
+        }
+
+        AuthenticationManager.AuthResult cookieResult = AuthenticationManager.authenticateIdentityCookie(session, realmModel, true);
+        String errorParam = "link_error";
+        if (cookieResult == null) {
+            event.error(Errors.NOT_LOGGED_IN);
+            UriBuilder builder = UriBuilder.fromUri(redirectUri)
+                    .queryParam(errorParam, Errors.NOT_LOGGED_IN)
+                    .queryParam("nonce", nonce);
+
+            return Response.status(302).location(builder.build()).build();
+        }
+
+
+
+        AuthenticatedClientSessionModel clientSession = null;
+        for (AuthenticatedClientSessionModel cs : cookieResult.getSession().getAuthenticatedClientSessions().values()) {
+            if (cs.getClient().getClientId().equals(clientId)) {
+                byte[] decoded = Base64Url.decode(hash);
+                MessageDigest md = null;
+                try {
+                    md = MessageDigest.getInstance("SHA-256");
+                } catch (NoSuchAlgorithmException e) {
+                    throw new ErrorPageException(session, Messages.UNEXPECTED_ERROR_HANDLING_REQUEST);
+                }
+                String input = nonce + cookieResult.getSession().getId() + clientId + providerId;
+                byte[] check = md.digest(input.getBytes(StandardCharsets.UTF_8));
+                if (MessageDigest.isEqual(decoded, check)) {
+                    clientSession = cs;
+                    break;
+                }
+            }
+        }
+        if (clientSession == null) {
+            event.error(Errors.INVALID_TOKEN);
+            throw new ErrorPageException(session, Messages.INVALID_REQUEST);
+        }
+
+
+
+        ClientModel accountService = this.realmModel.getClientByClientId(Constants.ACCOUNT_MANAGEMENT_CLIENT_ID);
+        if (!accountService.getId().equals(client.getId())) {
+            RoleModel manageAccountRole = accountService.getRole(AccountRoles.MANAGE_ACCOUNT);
+
+            if (!clientSession.getRoles().contains(manageAccountRole.getId())) {
+                RoleModel linkRole = accountService.getRole(AccountRoles.MANAGE_ACCOUNT_LINKS);
+                if (!clientSession.getRoles().contains(linkRole.getId())) {
+                    event.error(Errors.NOT_ALLOWED);
+                    UriBuilder builder = UriBuilder.fromUri(redirectUri)
+                            .queryParam(errorParam, Errors.NOT_ALLOWED)
+                            .queryParam("nonce", nonce);
+                    return Response.status(302).location(builder.build()).build();
+                }
+            }
+        }
+
+
+        IdentityProviderModel identityProviderModel = realmModel.getIdentityProviderByAlias(providerId);
+        if (identityProviderModel == null) {
+            event.error(Errors.UNKNOWN_IDENTITY_PROVIDER);
+            UriBuilder builder = UriBuilder.fromUri(redirectUri)
+                    .queryParam(errorParam, Errors.UNKNOWN_IDENTITY_PROVIDER)
+                    .queryParam("nonce", nonce);
+            return Response.status(302).location(builder.build()).build();
+
+        }
+
+
+        // Create AuthenticationSessionModel with same ID like userSession and refresh cookie
+        UserSessionModel userSession = cookieResult.getSession();
+        AuthenticationSessionModel authSession = session.authenticationSessions().createAuthenticationSession(userSession.getId(), realmModel, client);
+        new AuthenticationSessionManager(session).setAuthSessionCookie(userSession.getId(), realmModel);
+
+        ClientSessionCode<AuthenticationSessionModel> clientSessionCode = new ClientSessionCode<>(session, realmModel, authSession);
+        clientSessionCode.setAction(AuthenticationSessionModel.Action.AUTHENTICATE.name());
+        clientSessionCode.getCode();
+        authSession.setProtocol(client.getProtocol());
+        authSession.setRedirectUri(redirectUri);
+        authSession.setClientNote(OIDCLoginProtocol.STATE_PARAM, UUID.randomUUID().toString());
+        authSession.setAuthNote(LINKING_IDENTITY_PROVIDER, cookieResult.getSession().getId() + clientId + providerId);
+
+        event.success();
+
+        try {
+            IdentityProvider identityProvider = getIdentityProvider(session, realmModel, providerId);
+            Response response = identityProvider.performLogin(createAuthenticationRequest(providerId, clientSessionCode));
+
+            if (response != null) {
+                if (isDebugEnabled()) {
+                    logger.debugf("Identity provider [%s] is going to send a request [%s].", identityProvider, response);
+                }
+                return response;
+            }
+        } catch (IdentityBrokerException e) {
+            return redirectToErrorPage(Messages.COULD_NOT_SEND_AUTHENTICATION_REQUEST, e, providerId);
+        } catch (Exception e) {
+            return redirectToErrorPage(Messages.UNEXPECTED_ERROR_HANDLING_REQUEST, e, providerId);
+        }
+
+        return redirectToErrorPage(Messages.COULD_NOT_PROCEED_WITH_AUTHENTICATION_REQUEST);
+
+    }
+
+
+    @POST
+    @Path("/{provider_id}/login")
+    public Response performPostLogin(@PathParam("provider_id") String providerId, @QueryParam("code") String code) {
+        return performLogin(providerId, code);
+    }
+
+    @GET
+    @NoCache
+    @Path("/{provider_id}/login")
+    public Response performLogin(@PathParam("provider_id") String providerId, @QueryParam("code") String code) {
+        this.event.detail(Details.IDENTITY_PROVIDER, providerId);
+
+        if (isDebugEnabled()) {
+            logger.debugf("Sending authentication request to identity provider [%s].", providerId);
+        }
+
+        try {
+            ParsedCodeContext parsedCode = parseClientSessionCode(code);
+            if (parsedCode.response != null) {
+                return parsedCode.response;
+            }
+
+            ClientSessionCode clientSessionCode = parsedCode.clientSessionCode;
+            IdentityProviderModel identityProviderModel = realmModel.getIdentityProviderByAlias(providerId);
+            if (identityProviderModel == null) {
+                throw new IdentityBrokerException("Identity Provider [" + providerId + "] not found.");
+            }
+            if (identityProviderModel.isLinkOnly()) {
+                throw new IdentityBrokerException("Identity Provider [" + providerId + "] is not allowed to perform a login.");
+
+            }
+            IdentityProviderFactory providerFactory = getIdentityProviderFactory(session, identityProviderModel);
+
+            IdentityProvider identityProvider = providerFactory.create(session, identityProviderModel);
+
+            Response response = identityProvider.performLogin(createAuthenticationRequest(providerId, clientSessionCode));
+
+            if (response != null) {
+                if (isDebugEnabled()) {
+                    logger.debugf("Identity provider [%s] is going to send a request [%s].", identityProvider, response);
+                }
+                return response;
+            }
+        } catch (IdentityBrokerException e) {
+            return redirectToErrorPage(Messages.COULD_NOT_SEND_AUTHENTICATION_REQUEST, e, providerId);
+        } catch (Exception e) {
+            return redirectToErrorPage(Messages.UNEXPECTED_ERROR_HANDLING_REQUEST, e, providerId);
+        }
+
+        return redirectToErrorPage(Messages.COULD_NOT_PROCEED_WITH_AUTHENTICATION_REQUEST);
+    }
+
+    @Path("{provider_id}/endpoint")
+    public Object getEndpoint(@PathParam("provider_id") String providerId) {
+        IdentityProvider identityProvider = getIdentityProvider(session, realmModel, providerId);
+        Object callback = identityProvider.callback(realmModel, this, event);
+        ResteasyProviderFactory.getInstance().injectProperties(callback);
+        //resourceContext.initResource(brokerService);
+        return callback;
+
+
+    }
+
+    @Path("{provider_id}/token")
+    @OPTIONS
+    public Response retrieveTokenPreflight() {
+        return Cors.add(this.request, Response.ok()).auth().preflight().build();
+    }
+
+    @GET
+    @NoCache
+    @Path("{provider_id}/token")
+    public Response retrieveToken(@PathParam("provider_id") String providerId) {
+        return getToken(providerId, false);
+    }
+
+    private boolean canReadBrokerToken(AccessToken token) {
+        Map<String, AccessToken.Access> resourceAccess = token.getResourceAccess();
+        AccessToken.Access brokerRoles = resourceAccess == null ? null : resourceAccess.get(Constants.BROKER_SERVICE_CLIENT_ID);
+        return brokerRoles != null && brokerRoles.isUserInRole(Constants.READ_TOKEN_ROLE);
+    }
+
+    private Response getToken(String providerId, boolean forceRetrieval) {
+        this.event.event(EventType.IDENTITY_PROVIDER_RETRIEVE_TOKEN);
+
+        try {
+            AppAuthManager authManager = new AppAuthManager();
+            AuthenticationManager.AuthResult authResult = authManager.authenticateBearerToken(this.session, this.realmModel, this.uriInfo, this.clientConnection, this.request.getHttpHeaders());
+
+            if (authResult != null) {
+                AccessToken token = authResult.getToken();
+                String[] audience = token.getAudience();
+                ClientModel clientModel = this.realmModel.getClientByClientId(audience[0]);
+
+                if (clientModel == null) {
+                    return badRequest("Invalid client.");
+                }
+
+                session.getContext().setClient(clientModel);
+
+                ClientModel brokerClient = realmModel.getClientByClientId(Constants.BROKER_SERVICE_CLIENT_ID);
+                if (brokerClient == null) {
+                    return corsResponse(forbidden("Realm has not migrated to support the broker token exchange service"), clientModel);
+
+                }
+                if (!canReadBrokerToken(token)) {
+                    return corsResponse(forbidden("Client [" + clientModel.getClientId() + "] not authorized to retrieve tokens from identity provider [" + providerId + "]."), clientModel);
+
+                }
+
+                IdentityProvider identityProvider = getIdentityProvider(session, realmModel, providerId);
+                IdentityProviderModel identityProviderConfig = getIdentityProviderConfig(providerId);
+
+                if (identityProviderConfig.isStoreToken()) {
+                    FederatedIdentityModel identity = this.session.users().getFederatedIdentity(authResult.getUser(), providerId, this.realmModel);
+
+                    if (identity == null) {
+                        return corsResponse(badRequest("User [" + authResult.getUser().getId() + "] is not associated with identity provider [" + providerId + "]."), clientModel);
+                    }
+
+                    this.event.success();
+
+                    return corsResponse(identityProvider.retrieveToken(session, identity), clientModel);
+                }
+
+                return corsResponse(badRequest("Identity Provider [" + providerId + "] does not support this operation."), clientModel);
+            }
+
+            return badRequest("Invalid token.");
+        } catch (IdentityBrokerException e) {
+            return redirectToErrorPage(Messages.COULD_NOT_OBTAIN_TOKEN, e, providerId);
+        }  catch (Exception e) {
+            return redirectToErrorPage(Messages.UNEXPECTED_ERROR_RETRIEVING_TOKEN, e, providerId);
+        }
+    }
+
+    public Response authenticated(BrokeredIdentityContext context) {
+        IdentityProviderModel identityProviderConfig = context.getIdpConfig();
+
+        final ParsedCodeContext parsedCode;
+        if (context.getContextData().get(SAMLEndpoint.SAML_IDP_INITIATED_CLIENT_ID) != null) {
+            parsedCode = samlIdpInitiatedSSO((String) context.getContextData().get(SAMLEndpoint.SAML_IDP_INITIATED_CLIENT_ID));
+        } else {
+            parsedCode = parseClientSessionCode(context.getCode());
+        }
+        if (parsedCode.response != null) {
+            return parsedCode.response;
+        }
+        ClientSessionCode<AuthenticationSessionModel> clientCode = parsedCode.clientSessionCode;
+
+        String providerId = identityProviderConfig.getAlias();
+        if (!identityProviderConfig.isStoreToken()) {
+            if (isDebugEnabled()) {
+                logger.debugf("Token will not be stored for identity provider [%s].", providerId);
+            }
+            context.setToken(null);
+        }
+
+        AuthenticationSessionModel authenticationSession = clientCode.getClientSession();
+        context.setAuthenticationSession(authenticationSession);
+
+        session.getContext().setClient(authenticationSession.getClient());
+
+        context.getIdp().preprocessFederatedIdentity(session, realmModel, context);
+        Set<IdentityProviderMapperModel> mappers = realmModel.getIdentityProviderMappersByAlias(context.getIdpConfig().getAlias());
+        if (mappers != null) {
+            KeycloakSessionFactory sessionFactory = session.getKeycloakSessionFactory();
+            for (IdentityProviderMapperModel mapper : mappers) {
+                IdentityProviderMapper target = (IdentityProviderMapper)sessionFactory.getProviderFactory(IdentityProviderMapper.class, mapper.getIdentityProviderMapper());
+                target.preprocessFederatedIdentity(session, realmModel, mapper, context);
+            }
+        }
+
+        FederatedIdentityModel federatedIdentityModel = new FederatedIdentityModel(providerId, context.getId(),
+                context.getUsername(), context.getToken());
+
+        this.event.event(EventType.IDENTITY_PROVIDER_LOGIN)
+                .detail(Details.REDIRECT_URI, authenticationSession.getRedirectUri())
+                .detail(Details.IDENTITY_PROVIDER_USERNAME, context.getUsername());
+
+        UserModel federatedUser = this.session.users().getUserByFederatedIdentity(federatedIdentityModel, this.realmModel);
+
+        // Check if federatedUser is already authenticated (this means linking social into existing federatedUser account)
+        UserSessionModel userSession = new AuthenticationSessionManager(session).getUserSession(authenticationSession);
+        if (shouldPerformAccountLinking(authenticationSession, userSession, providerId)) {
+            return performAccountLinking(authenticationSession, userSession, context, federatedIdentityModel, federatedUser);
+        }
+
+        if (federatedUser == null) {
+
+            logger.debugf("Federated user not found for provider '%s' and broker username '%s' . Redirecting to flow for firstBrokerLogin", providerId, context.getUsername());
+
+            String username = context.getModelUsername();
+            if (username == null) {
+                if (this.realmModel.isRegistrationEmailAsUsername() && !Validation.isBlank(context.getEmail())) {
+                    username = context.getEmail();
+                } else if (context.getUsername() == null) {
+                    username = context.getIdpConfig().getAlias() + "." + context.getId();
+                } else {
+                    username = context.getUsername();
+                }
+            }
+            username = username.trim();
+            context.setModelUsername(username);
+
+            // Redirect to firstBrokerLogin after successful login and ensure that previous authentication state removed
+            AuthenticationProcessor.resetFlow(authenticationSession, LoginActionsService.FIRST_BROKER_LOGIN_PATH);
+
+            SerializedBrokeredIdentityContext ctx = SerializedBrokeredIdentityContext.serialize(context);
+            ctx.saveToAuthenticationSession(authenticationSession, AbstractIdpAuthenticator.BROKERED_CONTEXT_NOTE);
+
+            URI redirect = LoginActionsService.firstBrokerLoginProcessor(uriInfo)
+                    .build(realmModel.getName());
+            return Response.status(302).location(redirect).build();
+
+        } else {
+            Response response = validateUser(federatedUser, realmModel);
+            if (response != null) {
+                return response;
+            }
+
+            updateFederatedIdentity(context, federatedUser);
+            authenticationSession.setAuthenticatedUser(federatedUser);
+
+            return finishOrRedirectToPostBrokerLogin(authenticationSession, context, false, parsedCode.clientSessionCode);
+        }
+    }
+
+
+    public Response validateUser(UserModel user, RealmModel realm) {
+        if (!user.isEnabled()) {
+            event.error(Errors.USER_DISABLED);
+            return ErrorPage.error(session, 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 null;
+    }
+
+    // Callback from LoginActionsService after first login with broker was done and Keycloak account is successfully linked/created
+    @GET
+    @NoCache
+    @Path("/after-first-broker-login")
+    public Response afterFirstBrokerLogin(@QueryParam("code") String code) {
+        ParsedCodeContext parsedCode = parseClientSessionCode(code);
+        if (parsedCode.response != null) {
+            return parsedCode.response;
+        }
+        return afterFirstBrokerLogin(parsedCode.clientSessionCode);
+    }
+
+    private Response afterFirstBrokerLogin(ClientSessionCode<AuthenticationSessionModel> clientSessionCode) {
+        AuthenticationSessionModel authSession = clientSessionCode.getClientSession();
+
+        try {
+            this.event.detail(Details.CODE_ID, authSession.getId())
+                    .removeDetail("auth_method");
+
+            SerializedBrokeredIdentityContext serializedCtx = SerializedBrokeredIdentityContext.readFromAuthenticationSession(authSession, AbstractIdpAuthenticator.BROKERED_CONTEXT_NOTE);
+            if (serializedCtx == null) {
+                throw new IdentityBrokerException("Not found serialized context in clientSession");
+            }
+            BrokeredIdentityContext context = serializedCtx.deserialize(session, authSession);
+            String providerId = context.getIdpConfig().getAlias();
+
+            event.detail(Details.IDENTITY_PROVIDER, providerId);
+            event.detail(Details.IDENTITY_PROVIDER_USERNAME, context.getUsername());
+
+            // Ensure the first-broker-login flow was successfully finished
+            String authProvider = authSession.getAuthNote(AbstractIdpAuthenticator.FIRST_BROKER_LOGIN_SUCCESS);
+            if (authProvider == null || !authProvider.equals(providerId)) {
+                throw new IdentityBrokerException("Invalid request. Not found the flag that first-broker-login flow was finished");
+            }
+
+            // firstBrokerLogin workflow finished. Removing note now
+            authSession.removeAuthNote(AbstractIdpAuthenticator.BROKERED_CONTEXT_NOTE);
+
+            UserModel federatedUser = authSession.getAuthenticatedUser();
+            if (federatedUser == null) {
+                throw new IdentityBrokerException("Couldn't found authenticated federatedUser in authentication session");
+            }
+
+            event.user(federatedUser);
+            event.detail(Details.USERNAME, federatedUser.getUsername());
+
+            if (context.getIdpConfig().isAddReadTokenRoleOnCreate()) {
+                ClientModel brokerClient = realmModel.getClientByClientId(Constants.BROKER_SERVICE_CLIENT_ID);
+                if (brokerClient == null) {
+                    throw new IdentityBrokerException("Client 'broker' not available. Maybe realm has not migrated to support the broker token exchange service");
+                }
+                RoleModel readTokenRole = brokerClient.getRole(Constants.READ_TOKEN_ROLE);
+                federatedUser.grantRole(readTokenRole);
+            }
+
+            // Add federated identity link here
+            FederatedIdentityModel federatedIdentityModel = new FederatedIdentityModel(context.getIdpConfig().getAlias(), context.getId(),
+                    context.getUsername(), context.getToken());
+            session.users().addFederatedIdentity(realmModel, federatedUser, federatedIdentityModel);
+
+
+            String isRegisteredNewUser = authSession.getAuthNote(AbstractIdpAuthenticator.BROKER_REGISTERED_NEW_USER);
+            if (Boolean.parseBoolean(isRegisteredNewUser)) {
+
+                logger.debugf("Registered new user '%s' after first login with identity provider '%s'. Identity provider username is '%s' . ", federatedUser.getUsername(), providerId, context.getUsername());
+
+                context.getIdp().importNewUser(session, realmModel, federatedUser, context);
+                Set<IdentityProviderMapperModel> mappers = realmModel.getIdentityProviderMappersByAlias(providerId);
+                if (mappers != null) {
+                    KeycloakSessionFactory sessionFactory = session.getKeycloakSessionFactory();
+                    for (IdentityProviderMapperModel mapper : mappers) {
+                        IdentityProviderMapper target = (IdentityProviderMapper)sessionFactory.getProviderFactory(IdentityProviderMapper.class, mapper.getIdentityProviderMapper());
+                        target.importNewUser(session, realmModel, federatedUser, mapper, context);
+                    }
+                }
+
+                if (context.getIdpConfig().isTrustEmail() && !Validation.isBlank(federatedUser.getEmail()) && !Boolean.parseBoolean(authSession.getAuthNote(AbstractIdpAuthenticator.UPDATE_PROFILE_EMAIL_CHANGED))) {
+                    logger.debugf("Email verified automatically after registration of user '%s' through Identity provider '%s' ", federatedUser.getUsername(), context.getIdpConfig().getAlias());
+                    federatedUser.setEmailVerified(true);
+                }
+
+                event.event(EventType.REGISTER)
+                        .detail(Details.REGISTER_METHOD, "broker")
+                        .detail(Details.EMAIL, federatedUser.getEmail())
+                        .success();
+
+            } else {
+                logger.debugf("Linked existing keycloak user '%s' with identity provider '%s' . Identity provider username is '%s' .", federatedUser.getUsername(), providerId, context.getUsername());
+
+                event.event(EventType.FEDERATED_IDENTITY_LINK)
+                        .success();
+
+                updateFederatedIdentity(context, federatedUser);
+            }
+
+            return finishOrRedirectToPostBrokerLogin(authSession, context, true, clientSessionCode);
+
+        }  catch (Exception e) {
+            return redirectToErrorPage(Messages.IDENTITY_PROVIDER_UNEXPECTED_ERROR, e);
+        }
+    }
+
+
+    private Response finishOrRedirectToPostBrokerLogin(AuthenticationSessionModel authSession, BrokeredIdentityContext context, boolean wasFirstBrokerLogin, ClientSessionCode<AuthenticationSessionModel> clientSessionCode) {
+        String postBrokerLoginFlowId = context.getIdpConfig().getPostBrokerLoginFlowId();
+        if (postBrokerLoginFlowId == null) {
+
+            logger.debugf("Skip redirect to postBrokerLogin flow. PostBrokerLogin flow not set for identityProvider '%s'.", context.getIdpConfig().getAlias());
+            return afterPostBrokerLoginFlowSuccess(authSession, context, wasFirstBrokerLogin, clientSessionCode);
+        } else {
+
+            logger.debugf("Redirect to postBrokerLogin flow after authentication with identityProvider '%s'.", context.getIdpConfig().getAlias());
+
+            authSession.setTimestamp(Time.currentTime());
+
+            SerializedBrokeredIdentityContext ctx = SerializedBrokeredIdentityContext.serialize(context);
+            ctx.saveToAuthenticationSession(authSession, PostBrokerLoginConstants.PBL_BROKERED_IDENTITY_CONTEXT);
+
+            authSession.setAuthNote(PostBrokerLoginConstants.PBL_AFTER_FIRST_BROKER_LOGIN, String.valueOf(wasFirstBrokerLogin));
+
+            URI redirect = LoginActionsService.postBrokerLoginProcessor(uriInfo)
+                    .build(realmModel.getName());
+            return Response.status(302).location(redirect).build();
+        }
+    }
+
+
+    // Callback from LoginActionsService after postBrokerLogin flow is finished
+    @GET
+    @NoCache
+    @Path("/after-post-broker-login")
+    public Response afterPostBrokerLoginFlow(@QueryParam("code") String code) {
+        ParsedCodeContext parsedCode = parseClientSessionCode(code);
+        if (parsedCode.response != null) {
+            return parsedCode.response;
+        }
+        AuthenticationSessionModel authenticationSession = parsedCode.clientSessionCode.getClientSession();
+
+        try {
+            SerializedBrokeredIdentityContext serializedCtx = SerializedBrokeredIdentityContext.readFromAuthenticationSession(authenticationSession, PostBrokerLoginConstants.PBL_BROKERED_IDENTITY_CONTEXT);
+            if (serializedCtx == null) {
+                throw new IdentityBrokerException("Not found serialized context in clientSession. Note " + PostBrokerLoginConstants.PBL_BROKERED_IDENTITY_CONTEXT + " was null");
+            }
+            BrokeredIdentityContext context = serializedCtx.deserialize(session, authenticationSession);
+
+            String wasFirstBrokerLoginNote = authenticationSession.getAuthNote(PostBrokerLoginConstants.PBL_AFTER_FIRST_BROKER_LOGIN);
+            boolean wasFirstBrokerLogin = Boolean.parseBoolean(wasFirstBrokerLoginNote);
+
+            // Ensure the post-broker-login flow was successfully finished
+            String authStateNoteKey = PostBrokerLoginConstants.PBL_AUTH_STATE_PREFIX + context.getIdpConfig().getAlias();
+            String authState = authenticationSession.getAuthNote(authStateNoteKey);
+            if (!Boolean.parseBoolean(authState)) {
+                throw new IdentityBrokerException("Invalid request. Not found the flag that post-broker-login flow was finished");
+            }
+
+            // remove notes
+            authenticationSession.removeAuthNote(PostBrokerLoginConstants.PBL_BROKERED_IDENTITY_CONTEXT);
+            authenticationSession.removeAuthNote(PostBrokerLoginConstants.PBL_AFTER_FIRST_BROKER_LOGIN);
+
+            return afterPostBrokerLoginFlowSuccess(authenticationSession, context, wasFirstBrokerLogin, parsedCode.clientSessionCode);
+        } catch (IdentityBrokerException e) {
+            return redirectToErrorPage(Messages.IDENTITY_PROVIDER_UNEXPECTED_ERROR, e);
+        }
+    }
+
+    private Response afterPostBrokerLoginFlowSuccess(AuthenticationSessionModel authSession, BrokeredIdentityContext context, boolean wasFirstBrokerLogin, ClientSessionCode<AuthenticationSessionModel> clientSessionCode) {
+        String providerId = context.getIdpConfig().getAlias();
+        UserModel federatedUser = authSession.getAuthenticatedUser();
+
+        if (wasFirstBrokerLogin) {
+
+            String isDifferentBrowser = authSession.getAuthNote(AbstractIdpAuthenticator.IS_DIFFERENT_BROWSER);
+            if (Boolean.parseBoolean(isDifferentBrowser)) {
+                new AuthenticationSessionManager(session).removeAuthenticationSession(realmModel, authSession, true);
+                return session.getProvider(LoginFormsProvider.class)
+                        .setSuccess(Messages.IDENTITY_PROVIDER_LINK_SUCCESS, context.getIdpConfig().getAlias(), context.getUsername())
+                        .createInfoPage();
+            } else {
+                return finishBrokerAuthentication(context, federatedUser, authSession, providerId);
+            }
+
+        } else {
+
+            boolean firstBrokerLoginInProgress = (authSession.getAuthNote(AbstractIdpAuthenticator.BROKERED_CONTEXT_NOTE) != null);
+            if (firstBrokerLoginInProgress) {
+                logger.debugf("Reauthenticated with broker '%s' when linking user '%s' with other broker", context.getIdpConfig().getAlias(), federatedUser.getUsername());
+
+                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());
+                }
+
+                SerializedBrokeredIdentityContext serializedCtx = SerializedBrokeredIdentityContext.readFromAuthenticationSession(authSession, AbstractIdpAuthenticator.BROKERED_CONTEXT_NOTE);
+                authSession.setAuthNote(AbstractIdpAuthenticator.FIRST_BROKER_LOGIN_SUCCESS, serializedCtx.getIdentityProviderId());
+
+                return afterFirstBrokerLogin(clientSessionCode);
+            } else {
+                return finishBrokerAuthentication(context, federatedUser, authSession, providerId);
+            }
+        }
+    }
+
+
+    private Response finishBrokerAuthentication(BrokeredIdentityContext context, UserModel federatedUser, AuthenticationSessionModel authSession, String providerId) {
+        authSession.setAuthNote(AuthenticationProcessor.BROKER_SESSION_ID, context.getBrokerSessionId());
+        authSession.setAuthNote(AuthenticationProcessor.BROKER_USER_ID, context.getBrokerUserId());
+
+        this.event.user(federatedUser);
+
+        context.getIdp().authenticationFinished(authSession, context);
+        authSession.setUserSessionNote(Details.IDENTITY_PROVIDER, providerId);
+        authSession.setUserSessionNote(Details.IDENTITY_PROVIDER_USERNAME, context.getUsername());
+
+        if (isDebugEnabled()) {
+            logger.debugf("Performing local authentication for user [%s].", federatedUser);
+        }
+
+        AuthenticationManager.setRolesAndMappersInSession(authSession);
+
+        String nextRequiredAction = AuthenticationManager.nextRequiredAction(session, authSession, clientConnection, request, uriInfo, event);
+        if (nextRequiredAction != null) {
+            return AuthenticationManager.redirectToRequiredActions(session, realmModel, authSession, uriInfo, nextRequiredAction);
+        } else {
+            event.detail(Details.CODE_ID, authSession.getId());  // todo This should be set elsewhere.  find out why tests fail.  Don't know where this is supposed to be set
+            return AuthenticationManager.finishedRequiredActions(session, authSession, null, clientConnection, request, uriInfo, event);
+        }
+    }
+
+
+    @Override
+    public Response cancelled(String code) {
+        ParsedCodeContext parsedCode = parseClientSessionCode(code);
+        if (parsedCode.response != null) {
+            return parsedCode.response;
+        }
+        ClientSessionCode<AuthenticationSessionModel> clientCode = parsedCode.clientSessionCode;
+
+        Response accountManagementFailedLinking = checkAccountManagementFailedLinking(clientCode.getClientSession(), Messages.CONSENT_DENIED);
+        if (accountManagementFailedLinking != null) {
+            return accountManagementFailedLinking;
+        }
+
+        return browserAuthentication(clientCode.getClientSession(), null);
+    }
+
+    @Override
+    public Response error(String code, String message) {
+        ParsedCodeContext parsedCode = parseClientSessionCode(code);
+        if (parsedCode.response != null) {
+            return parsedCode.response;
+        }
+        ClientSessionCode<AuthenticationSessionModel> clientCode = parsedCode.clientSessionCode;
+
+        Response accountManagementFailedLinking = checkAccountManagementFailedLinking(clientCode.getClientSession(), message);
+        if (accountManagementFailedLinking != null) {
+            return accountManagementFailedLinking;
+        }
+
+        return browserAuthentication(clientCode.getClientSession(), message);
+    }
+
+
+    private boolean shouldPerformAccountLinking(AuthenticationSessionModel authSession, UserSessionModel userSession, String providerId) {
+        String noteFromSession = authSession.getAuthNote(LINKING_IDENTITY_PROVIDER);
+        if (noteFromSession == null) {
+            return false;
+        }
+
+        boolean linkingValid;
+        if (userSession == null) {
+            linkingValid = false;
+        } else {
+            String expectedNote = userSession.getId() + authSession.getClient().getClientId() + providerId;
+            linkingValid = expectedNote.equals(noteFromSession);
+        }
+
+        if (linkingValid) {
+            authSession.removeAuthNote(LINKING_IDENTITY_PROVIDER);
+            return true;
+        } else {
+            throw new ErrorPageException(session, Messages.BROKER_LINKING_SESSION_EXPIRED);
+        }
+    }
+
+
+    private Response performAccountLinking(AuthenticationSessionModel authSession, UserSessionModel userSession, BrokeredIdentityContext context, FederatedIdentityModel newModel, UserModel federatedUser) {
+        logger.debugf("Will try to link identity provider [%s] to user [%s]", context.getIdpConfig().getAlias(), userSession.getUser().getUsername());
+
+        this.event.event(EventType.FEDERATED_IDENTITY_LINK);
+
+
+
+        UserModel authenticatedUser = userSession.getUser();
+        authSession.setAuthenticatedUser(authenticatedUser);
+
+        if (federatedUser != null && !authenticatedUser.getId().equals(federatedUser.getId())) {
+            return redirectToErrorWhenLinkingFailed(authSession, Messages.IDENTITY_PROVIDER_ALREADY_LINKED, context.getIdpConfig().getAlias());
+        }
+
+        if (!authenticatedUser.hasRole(this.realmModel.getClientByClientId(Constants.ACCOUNT_MANAGEMENT_CLIENT_ID).getRole(AccountRoles.MANAGE_ACCOUNT))) {
+            return redirectToErrorPage(Messages.INSUFFICIENT_PERMISSION);
+        }
+
+        if (!authenticatedUser.isEnabled()) {
+            return redirectToErrorWhenLinkingFailed(authSession, Messages.ACCOUNT_DISABLED);
+        }
+
+
+
+        if (federatedUser != null) {
+            if (context.getIdpConfig().isStoreToken()) {
+                FederatedIdentityModel oldModel = this.session.users().getFederatedIdentity(federatedUser, context.getIdpConfig().getAlias(), this.realmModel);
+                if (!ObjectUtil.isEqualOrBothNull(context.getToken(), oldModel.getToken())) {
+                    this.session.users().updateFederatedIdentity(this.realmModel, federatedUser, newModel);
+                    if (isDebugEnabled()) {
+                        logger.debugf("Identity [%s] update with response from identity provider [%s].", federatedUser, context.getIdpConfig().getAlias());
+                    }
+                }
+            }
+        } else {
+            this.session.users().addFederatedIdentity(this.realmModel, authenticatedUser, newModel);
+        }
+        context.getIdp().authenticationFinished(authSession, context);
+
+        AuthenticationManager.setRolesAndMappersInSession(authSession);
+        TokenManager.attachAuthenticationSession(session, userSession, authSession);
+
+        if (isDebugEnabled()) {
+            logger.debugf("Linking account [%s] from identity provider [%s] to user [%s].", newModel, context.getIdpConfig().getAlias(), authenticatedUser);
+        }
+
+        this.event.user(authenticatedUser)
+                .detail(Details.USERNAME, authenticatedUser.getUsername())
+                .detail(Details.IDENTITY_PROVIDER, newModel.getIdentityProvider())
+                .detail(Details.IDENTITY_PROVIDER_USERNAME, newModel.getUserName())
+                .success();
+
+        // we do this to make sure that the parent IDP is logged out when this user session is complete.
+        // But for the case when userSession was previously authenticated with broker1 and now is linked to another broker2, we shouldn't override broker1 notes with the broker2 for sure.
+        // Maybe broker logout should be rather always skiped in case of broker-linking
+        if (userSession.getNote(Details.IDENTITY_PROVIDER) == null) {
+            userSession.setNote(Details.IDENTITY_PROVIDER, context.getIdpConfig().getAlias());
+            userSession.setNote(Details.IDENTITY_PROVIDER_USERNAME, context.getUsername());
+        }
+
+        return Response.status(302).location(UriBuilder.fromUri(authSession.getRedirectUri()).build()).build();
+    }
+
+
+    private Response redirectToErrorWhenLinkingFailed(AuthenticationSessionModel authSession, String message, Object... parameters) {
+        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?
+        }
+    }
+
+
+    private void updateFederatedIdentity(BrokeredIdentityContext context, UserModel federatedUser) {
+        FederatedIdentityModel federatedIdentityModel = this.session.users().getFederatedIdentity(federatedUser, context.getIdpConfig().getAlias(), this.realmModel);
+
+        // Skip DB write if tokens are null or equal
+        updateToken(context, federatedUser, federatedIdentityModel);
+        context.getIdp().updateBrokeredUser(session, realmModel, federatedUser, context);
+        Set<IdentityProviderMapperModel> mappers = realmModel.getIdentityProviderMappersByAlias(context.getIdpConfig().getAlias());
+        if (mappers != null) {
+            KeycloakSessionFactory sessionFactory = session.getKeycloakSessionFactory();
+            for (IdentityProviderMapperModel mapper : mappers) {
+                IdentityProviderMapper target = (IdentityProviderMapper)sessionFactory.getProviderFactory(IdentityProviderMapper.class, mapper.getIdentityProviderMapper());
+                target.updateBrokeredUser(session, realmModel, federatedUser, mapper, context);
+            }
+        }
+
+    }
+
+    private void updateToken(BrokeredIdentityContext context, UserModel federatedUser, FederatedIdentityModel federatedIdentityModel) {
+        if (context.getIdpConfig().isStoreToken() && !ObjectUtil.isEqualOrBothNull(context.getToken(), federatedIdentityModel.getToken())) {
+            federatedIdentityModel.setToken(context.getToken());
+
+            this.session.users().updateFederatedIdentity(this.realmModel, federatedUser, federatedIdentityModel);
+
+            if (isDebugEnabled()) {
+                logger.debugf("Identity [%s] update with response from identity provider [%s].", federatedUser, context.getIdpConfig().getAlias());
+            }
+        }
+    }
+
+    private ParsedCodeContext parseClientSessionCode(String code) {
+        ClientSessionCode.ParseResult<AuthenticationSessionModel> parseResult = ClientSessionCode.parseResult(code, this.session, this.realmModel, AuthenticationSessionModel.class);
+        ClientSessionCode<AuthenticationSessionModel> clientCode = parseResult.getCode();
+
+        if (clientCode != null) {
+            AuthenticationSessionModel authenticationSession = clientCode.getClientSession();
+
+            ClientModel client = authenticationSession.getClient();
+
+            if (client != null) {
+
+                logger.debugf("Got authorization code from client [%s].", client.getClientId());
+                this.event.client(client);
+                this.session.getContext().setClient(client);
+
+                if (!clientCode.isValid(AuthenticationSessionModel.Action.AUTHENTICATE.name(), ClientSessionCode.ActionType.LOGIN)) {
+                    logger.debugf("Authorization code is not valid. Client session ID: %s, Client session's action: %s", authenticationSession.getId(), authenticationSession.getAction());
+
+                    // Check if error happened during login or during linking from account management
+                    Response accountManagementFailedLinking = checkAccountManagementFailedLinking(clientCode.getClientSession(), Messages.STALE_CODE_ACCOUNT);
+                    Response staleCodeError = (accountManagementFailedLinking != null) ? accountManagementFailedLinking : redirectToErrorPage(Messages.STALE_CODE);
+
+
+                    return ParsedCodeContext.response(staleCodeError);
+                }
+
+                if (isDebugEnabled()) {
+                    logger.debugf("Authorization code is valid.");
+                }
+
+                return ParsedCodeContext.clientSessionCode(clientCode);
+            }
+        }
+
+        // TODO:mposolda rather some different page? Maybe "PageExpired" page?
+        logger.debugf("Authorization code is not valid. Code: %s", code);
+        Response staleCodeError = redirectToErrorPage(Messages.STALE_CODE);
+        return ParsedCodeContext.response(staleCodeError);
+    }
+
+    /**
+     * If there is a client whose SAML IDP-initiated SSO URL name is set to the
+     * given {@code clientUrlName}, creates a fresh client session for that
+     * client and returns a {@link ParsedCodeContext} object with that session.
+     * Otherwise returns "client not found" response.
+     *
+     * @param clientUrlName
+     * @return see description
+     */
+    private ParsedCodeContext samlIdpInitiatedSSO(final String clientUrlName) {
+        event.event(EventType.LOGIN);
+        CacheControlUtil.noBackButtonCacheControlHeader();
+        Optional<ClientModel> oClient = this.realmModel.getClients().stream()
+          .filter(c -> Objects.equals(c.getAttribute(SamlProtocol.SAML_IDP_INITIATED_SSO_URL_NAME), clientUrlName))
+          .findFirst();
+
+        if (! oClient.isPresent()) {
+            event.error(Errors.CLIENT_NOT_FOUND);
+            return ParsedCodeContext.response(redirectToErrorPage(Messages.CLIENT_NOT_FOUND));
+        }
+
+        SamlService samlService = new SamlService(realmModel, event);
+        AuthenticationSessionModel authSession = samlService.getOrCreateLoginSessionForIdpInitiatedSso(session, realmModel, oClient.get(), null);
+
+        return ParsedCodeContext.clientSessionCode(new ClientSessionCode<>(session, this.realmModel, authSession));
+    }
+
+    private Response checkAccountManagementFailedLinking(AuthenticationSessionModel authSession, String error, Object... parameters) {
+        UserSessionModel userSession = new AuthenticationSessionManager(session).getUserSession(authSession);
+        if (userSession != null && authSession.getClient() != null && authSession.getClient().getClientId().equals(Constants.ACCOUNT_MANAGEMENT_CLIENT_ID)) {
+
+            this.event.event(EventType.FEDERATED_IDENTITY_LINK);
+            UserModel user = userSession.getUser();
+            this.event.user(user);
+            this.event.detail(Details.USERNAME, user.getUsername());
+
+            return redirectToAccountErrorPage(authSession, error, parameters);
+        } else {
+            return null;
+        }
+    }
+
+    private AuthenticationRequest createAuthenticationRequest(String providerId, ClientSessionCode<AuthenticationSessionModel> clientSessionCode) {
+        AuthenticationSessionModel authSession = null;
+        String relayState = null;
+
+        if (clientSessionCode != null) {
+            authSession = clientSessionCode.getClientSession();
+            relayState = clientSessionCode.getCode();
+        }
+
+        return new AuthenticationRequest(this.session, this.realmModel, authSession, this.request, this.uriInfo, relayState, getRedirectUri(providerId));
+    }
+
+    private String getRedirectUri(String providerId) {
+        return Urls.identityProviderAuthnResponse(this.uriInfo.getBaseUri(), providerId, this.realmModel.getName()).toString();
+    }
+
+    private Response redirectToErrorPage(String message, Object ... parameters) {
+        return redirectToErrorPage(message, null, parameters);
+    }
+
+    private Response redirectToErrorPage(String message, Throwable throwable, Object ... parameters) {
+        if (message == null) {
+            message = Messages.IDENTITY_PROVIDER_UNEXPECTED_ERROR;
+        }
+
+        fireErrorEvent(message, throwable);
+
+        if (throwable != null && throwable instanceof WebApplicationException) {
+            WebApplicationException webEx = (WebApplicationException) throwable;
+            return webEx.getResponse();
+        }
+
+        return ErrorPage.error(this.session, message, parameters);
+    }
+
+    private Response redirectToAccountErrorPage(AuthenticationSessionModel authSession, String message, Object ... parameters) {
+        fireErrorEvent(message);
+
+        FormMessage errorMessage = new FormMessage(message, parameters);
+        try {
+            String serializedError = JsonSerialization.writeValueAsString(errorMessage);
+            authSession.setAuthNote(AccountService.ACCOUNT_MGMT_FORWARDED_ERROR_NOTE, serializedError);
+        } catch (IOException ioe) {
+            throw new RuntimeException(ioe);
+        }
+
+        return Response.status(302).location(UriBuilder.fromUri(authSession.getRedirectUri()).build()).build();
+    }
+
+    private Response redirectToLoginPage(Throwable t, ClientSessionCode<AuthenticationSessionModel> clientCode) {
+        String message = t.getMessage();
+
+        if (message == null) {
+            message = Messages.IDENTITY_PROVIDER_UNEXPECTED_ERROR;
+        }
+
+        fireErrorEvent(message);
+        return browserAuthentication(clientCode.getClientSession(), message);
+    }
+
+    protected Response browserAuthentication(AuthenticationSessionModel authSession, String errorMessage) {
+        this.event.event(EventType.LOGIN);
+        AuthenticationFlowModel flow = realmModel.getBrowserFlow();
+        String flowId = flow.getId();
+        AuthenticationProcessor processor = new AuthenticationProcessor();
+        processor.setAuthenticationSession(authSession)
+                .setFlowPath(LoginActionsService.AUTHENTICATE_PATH)
+                .setFlowId(flowId)
+                .setBrowserFlow(true)
+                .setConnection(clientConnection)
+                .setEventBuilder(event)
+                .setRealm(realmModel)
+                .setSession(session)
+                .setUriInfo(uriInfo)
+                .setRequest(request);
+        if (errorMessage != null) processor.setForwardedErrorMessage(new FormMessage(null, errorMessage));
+
+        try {
+            CacheControlUtil.noBackButtonCacheControlHeader();
+            return processor.authenticate();
+        } catch (Exception e) {
+            return processor.handleBrowserException(e);
+        }
+    }
+
+
+    private Response badRequest(String message) {
+        fireErrorEvent(message);
+        return ErrorResponse.error(message, Response.Status.BAD_REQUEST);
+    }
+
+    private Response forbidden(String message) {
+        fireErrorEvent(message);
+        return ErrorResponse.error(message, Response.Status.FORBIDDEN);
+    }
 
     public static IdentityProvider getIdentityProvider(KeycloakSession session, RealmModel realm, String alias) {
         IdentityProviderModel identityProviderModel = realm.getIdentityProviderByAlias(alias);
@@ -1017,7 +1167,7 @@ public class IdentityBrokerService {
 
         return availableProviders.get(model.getProviderId());
     }
-/*
+
     private IdentityProviderModel getIdentityProviderConfig(String providerId) {
         IdentityProviderModel model = this.realmModel.getIdentityProviderByAlias(providerId);
         if (model == null) {
@@ -1073,10 +1223,10 @@ public class IdentityBrokerService {
 
 
     private static class ParsedCodeContext {
-        private ClientSessionCode<LoginSessionModel> clientSessionCode;
+        private ClientSessionCode<AuthenticationSessionModel> clientSessionCode;
         private Response response;
 
-        public static ParsedCodeContext clientSessionCode(ClientSessionCode<LoginSessionModel> clientSessionCode) {
+        public static ParsedCodeContext clientSessionCode(ClientSessionCode<AuthenticationSessionModel> clientSessionCode) {
             ParsedCodeContext ctx = new ParsedCodeContext();
             ctx.clientSessionCode = clientSessionCode;
             return ctx;
@@ -1088,5 +1238,5 @@ public class IdentityBrokerService {
             return ctx;
         }
     }
-    */
+
 }
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 6792964..23fc616 100755
--- a/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java
+++ b/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java
@@ -29,10 +29,14 @@ import org.keycloak.TokenVerifier.Predicate;
 import org.keycloak.TokenVerifier.TokenTypeCheck;
 import org.keycloak.authentication.*;
 import org.keycloak.authentication.authenticators.broker.AbstractIdpAuthenticator;
+import org.keycloak.authentication.authenticators.broker.util.PostBrokerLoginConstants;
+import org.keycloak.authentication.authenticators.broker.util.SerializedBrokeredIdentityContext;
 import org.keycloak.authentication.authenticators.browser.AbstractUsernameFormAuthenticator;
+import org.keycloak.broker.provider.BrokeredIdentityContext;
 import org.keycloak.common.ClientConnection;
 import org.keycloak.common.VerificationException;
 import org.keycloak.common.util.ObjectUtil;
+import org.keycloak.common.util.Time;
 import org.keycloak.events.Details;
 import org.keycloak.events.Errors;
 import org.keycloak.events.EventBuilder;
@@ -50,7 +54,9 @@ import org.keycloak.models.RealmModel;
 import org.keycloak.models.RoleModel;
 import org.keycloak.models.UserConsentModel;
 import org.keycloak.models.UserModel;
+import org.keycloak.models.UserSessionModel;
 import org.keycloak.models.utils.FormMessage;
+import org.keycloak.protocol.AuthorizationEndpointBase;
 import org.keycloak.protocol.LoginProtocol;
 import org.keycloak.protocol.LoginProtocol.Error;
 import org.keycloak.protocol.RestartLoginCookie;
@@ -62,11 +68,14 @@ import org.keycloak.services.ErrorPage;
 import org.keycloak.services.ServicesLogger;
 import org.keycloak.services.Urls;
 import org.keycloak.services.managers.AuthenticationManager;
+import org.keycloak.services.managers.AuthenticationSessionManager;
 import org.keycloak.services.managers.ClientSessionCode;
 import org.keycloak.services.messages.Messages;
 import org.keycloak.services.util.CacheControlUtil;
-import org.keycloak.services.util.CookieHelper;
+import org.keycloak.services.util.PageExpiredRedirect;
+import org.keycloak.services.util.BrowserHistoryHelper;
 import org.keycloak.sessions.AuthenticationSessionModel;
+import org.keycloak.sessions.CommonClientSessionModel;
 
 import org.keycloak.sessions.CommonClientSessionModel.Action;
 import javax.ws.rs.Consumes;
@@ -76,7 +85,6 @@ import javax.ws.rs.Path;
 import javax.ws.rs.QueryParam;
 import javax.ws.rs.WebApplicationException;
 import javax.ws.rs.core.Context;
-import javax.ws.rs.core.Cookie;
 import javax.ws.rs.core.HttpHeaders;
 import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.MultivaluedMap;
@@ -99,7 +107,6 @@ public class LoginActionsService {
 
     private static final Logger logger = Logger.getLogger(LoginActionsService.class);
 
-    public static final String ACTION_COOKIE = "KEYCLOAK_ACTION";
     public static final String AUTHENTICATE_PATH = "authenticate";
     public static final String REGISTRATION_PATH = "registration";
     public static final String RESET_CREDENTIALS_PATH = "reset-credentials";
@@ -107,6 +114,10 @@ public class LoginActionsService {
     public static final String FIRST_BROKER_LOGIN_PATH = "first-broker-login";
     public static final String POST_BROKER_LOGIN_PATH = "post-broker-login";
 
+    public static final String RESTART_PATH = "restart";
+
+    public static final String FORWARDED_ERROR_MESSAGE_NOTE = "forwardedErrorMessage";
+
     private RealmModel realm;
 
     @Context
@@ -172,9 +183,9 @@ public class LoginActionsService {
         }
     }
 
-    private SessionCodeChecks checksForCode(String code, String execution, String flowPath, boolean wantsRestartSession) {
-        SessionCodeChecks res = new SessionCodeChecks(code, execution, flowPath, wantsRestartSession);
-        res.initialVerifyCode();
+    private SessionCodeChecks checksForCode(String code, String execution, String flowPath) {
+        SessionCodeChecks res = new SessionCodeChecks(code, execution, flowPath);
+        res.initialVerify();
         return res;
     }
 
@@ -189,13 +200,11 @@ public class LoginActionsService {
         private final String code;
         private final String execution;
         private final String flowPath;
-        private final boolean wantsRestartSession;
 
-        public SessionCodeChecks(String code, String execution, String flowPath, boolean wantsRestartSession) {
+        public SessionCodeChecks(String code, String execution, String flowPath) {
             this.code = code;
             this.execution = execution;
             this.flowPath = flowPath;
-            this.wantsRestartSession = wantsRestartSession;
         }
 
         public AuthenticationSessionModel getAuthenticationSession() {
@@ -211,81 +220,109 @@ public class LoginActionsService {
         }
 
 
-        boolean verifyCode(String requiredAction, ClientSessionCode.ActionType actionType) {
+        boolean verifyCode(String expectedAction, ClientSessionCode.ActionType actionType) {
             if (failed()) {
                 return false;
             }
 
-            if (!clientCode.isValidAction(requiredAction)) {
+            if (!isActionActive(actionType)) {
+                return false;
+            }
+
+            if (!clientCode.isValidAction(expectedAction)) {
                 AuthenticationSessionModel authSession = getAuthenticationSession();
                 if (ClientSessionModel.Action.REQUIRED_ACTIONS.name().equals(authSession.getAction())) {
                     // TODO:mposolda debug or trace
-                    logger.info("Incorrect flow '%s' . User authenticated already. Trying requiredActions now.");
-                    response = AuthenticationManager.nextActionAfterAuthentication(session, authSession, clientConnection, request, uriInfo, event);
+                    logger.info("Incorrect flow '%s' . User authenticated already. Redirecting to requiredActions now.");
+                    response = redirectToRequiredActions(null);
                     return false;
-                } // TODO:mposolda
-                /*else if (clientSession.getUserSession() != null && clientSession.getUserSession().getState() == UserSessionModel.State.LOGGED_IN) {
-                    response = session.getProvider(LoginFormsProvider.class)
-                            .setSuccess(Messages.ALREADY_LOGGED_IN)
-                            .createInfoPage();
+                } else {
+                    // TODO:mposolda could this happen? Doublecheck if we use other AuthenticationSession.Action besides AUTHENTICATE and REQUIRED_ACTIONS
+                    logger.errorf("Bad action. Expected action '%s', current action '%s'", expectedAction, authSession.getAction());
+                    response = ErrorPage.error(session, Messages.EXPIRED_CODE);
                     return false;
-                }*/
+                }
             }
 
-            return isActionActive(actionType);
-        }
-
-        private boolean isValidAction(String requiredAction) {
-            if (!clientCode.isValidAction(requiredAction)) {
-                invalidAction();
-                return false;
-            }
             return true;
         }
 
-        private void invalidAction() {
-            event.client(getAuthenticationSession().getClient());
-            event.error(Errors.INVALID_CODE);
-            response = ErrorPage.error(session, Messages.INVALID_CODE);
-        }
 
         private boolean isActionActive(ClientSessionCode.ActionType actionType) {
             if (!clientCode.isActionActive(actionType)) {
                 event.client(getAuthenticationSession().getClient());
                 event.clone().error(Errors.EXPIRED_CODE);
-                if (getAuthenticationSession().getAction().equals(ClientSessionModel.Action.AUTHENTICATE.name())) {
-                    AuthenticationSessionModel authSession = getAuthenticationSession();
-                    AuthenticationProcessor.resetFlow(authSession);
-                    response = processAuthentication(false, null, authSession, Messages.LOGIN_TIMEOUT);
-                    return false;
-                }
-                response = ErrorPage.error(session, Messages.EXPIRED_CODE);
+
+                AuthenticationSessionModel authSession = getAuthenticationSession();
+                AuthenticationProcessor.resetFlow(authSession, AUTHENTICATE_PATH);
+                response = processAuthentication(false, null, authSession, Messages.LOGIN_TIMEOUT);
                 return false;
             }
             return true;
         }
 
-        private boolean initialVerifyCode() {
+
+        private AuthenticationSessionModel initialVerifyAuthSession() {
             // Basic realm checks
             if (!checkSsl()) {
                 event.error(Errors.SSL_REQUIRED);
                 response = ErrorPage.error(session, Messages.HTTPS_REQUIRED);
-                return false;
+                return null;
             }
             if (!realm.isEnabled()) {
                 event.error(Errors.REALM_DISABLED);
                 response = ErrorPage.error(session, Messages.REALM_NOT_ENABLED);
-                return false;
+                return null;
             }
 
-            // authenticationSession retrieve and check if we need session restart
+            // authenticationSession retrieve
             AuthenticationSessionModel authSession = ClientSessionCode.getClientSession(code, session, realm, AuthenticationSessionModel.class);
+            if (authSession != null) {
+                return authSession;
+            }
+
+            // See if we are already authenticated and userSession with same ID exists.
+            String sessionId = new AuthenticationSessionManager(session).getCurrentAuthenticationSessionId(realm);
+            if (sessionId != null) {
+                UserSessionModel userSession = session.sessions().getUserSession(realm, sessionId);
+                if (userSession != null) {
+
+                    LoginFormsProvider loginForm = session.getProvider(LoginFormsProvider.class)
+                            .setSuccess(Messages.ALREADY_LOGGED_IN);
+
+                    ClientModel client = null;
+                    String lastClientUuid = userSession.getNote(AuthenticationManager.LAST_AUTHENTICATED_CLIENT);
+                    if (lastClientUuid != null) {
+                        client = realm.getClientById(lastClientUuid);
+                    }
+
+                    if (client != null) {
+                        session.getContext().setClient(client);
+                    } else {
+                        loginForm.setAttribute("skipLink", true);
+                    }
+
+                    response = loginForm.createInfoPage();
+                    return null;
+                }
+            }
+
+            // Otherwise just try to restart from the cookie
+            response = restartAuthenticationSessionFromCookie();
+            return null;
+        }
+
+
+        private boolean initialVerify() {
+            // Basic realm checks and authenticationSession retrieve
+            AuthenticationSessionModel authSession = initialVerifyAuthSession();
             if (authSession == null) {
-                response = restartAuthenticationSession(false);
                 return false;
             }
-            if (wantsRestartSession) {
-                response = restartAuthenticationSession(true);
+
+            // Check cached response from previous action request
+            response = BrowserHistoryHelper.getInstance().loadSavedResponse(session, authSession);
+            if (response != null) {
                 return false;
             }
 
@@ -309,16 +346,16 @@ public class LoginActionsService {
 
             // Check if it's action or not
             if (code == null) {
-                String lastExecFromSession = authSession.getAuthNote(AuthenticationProcessor.LAST_PROCESSED_EXECUTION);
+                String lastExecFromSession = authSession.getAuthNote(AuthenticationProcessor.CURRENT_AUTHENTICATION_EXECUTION);
                 String lastFlow = authSession.getAuthNote(AuthenticationProcessor.CURRENT_FLOW_PATH);
 
                 // Check if we transitted between flows (eg. clicking "register" on login screen)
                 if (execution==null && !flowPath.equals(lastFlow)) {
                     logger.infof("Transition between flows! Current flow: %s, Previous flow: %s", flowPath, lastFlow);
 
-                    if (lastFlow == null || isFlowTransitionAllowed(lastFlow)) {
+                    if (lastFlow == null || isFlowTransitionAllowed(flowPath, lastFlow)) {
                         authSession.setAuthNote(AuthenticationProcessor.CURRENT_FLOW_PATH, flowPath);
-                        authSession.removeAuthNote(AuthenticationProcessor.LAST_PROCESSED_EXECUTION);
+                        authSession.removeAuthNote(AuthenticationProcessor.CURRENT_AUTHENTICATION_EXECUTION);
                         lastExecFromSession = null;
                     }
                 }
@@ -329,8 +366,7 @@ public class LoginActionsService {
                     actionRequest = false;
                     return true;
                 } else {
-                    logger.info("Redirecting to page expired page.");
-                    response = showPageExpired(flowPath, authSession);
+                    response = showPageExpired(authSession);
                     return false;
                 }
             } else {
@@ -339,13 +375,15 @@ public class LoginActionsService {
                 if (clientCode == null) {
 
                     // In case that is replayed action, but sent to the same FORM like actual FORM, we just re-render the page
-                    if (ObjectUtil.isEqualOrBothNull(execution, authSession.getAuthNote(AuthenticationProcessor.LAST_PROCESSED_EXECUTION))) {
+                    if (ObjectUtil.isEqualOrBothNull(execution, authSession.getAuthNote(AuthenticationProcessor.CURRENT_AUTHENTICATION_EXECUTION))) {
                         String latestFlowPath = authSession.getAuthNote(AuthenticationProcessor.CURRENT_FLOW_PATH);
                         URI redirectUri = getLastExecutionUrl(latestFlowPath, execution);
+
                         logger.infof("Invalid action code, but execution matches. So just redirecting to %s", redirectUri);
+                        authSession.setAuthNote(FORWARDED_ERROR_MESSAGE_NOTE, Messages.EXPIRED_ACTION);
                         response = Response.status(Response.Status.FOUND).location(redirectUri).build();
                     } else {
-                        response = showPageExpired(flowPath, authSession);
+                        response = showPageExpired(authSession);
                     }
                     return false;
                 }
@@ -357,38 +395,32 @@ public class LoginActionsService {
             }
         }
 
-        private boolean isFlowTransitionAllowed(String lastFlow) {
-            if (flowPath.equals(AUTHENTICATE_PATH) && (lastFlow.equals(REGISTRATION_PATH) || lastFlow.equals(RESET_CREDENTIALS_PATH))) {
-                return true;
-            }
 
-            if (flowPath.equals(REGISTRATION_PATH) && (lastFlow.equals(AUTHENTICATE_PATH))) {
-                return true;
+        public boolean verifyRequiredAction(String executedAction) {
+            if (failed()) {
+                return false;
             }
 
-            if (flowPath.equals(RESET_CREDENTIALS_PATH) && (lastFlow.equals(AUTHENTICATE_PATH))) {
-                return true;
-            }
+            if (!clientCode.isValidAction(ClientSessionModel.Action.REQUIRED_ACTIONS.name())) {
+                // TODO:mposolda debug or trace
+                logger.infof("Expected required action, but session action is '%s' . Showing expired page now.", getAuthenticationSession().getAction());
+                event.client(getAuthenticationSession().getClient());
+                event.error(Errors.INVALID_CODE);
 
-            return false;
-        }
+                response = showPageExpired(getAuthenticationSession());
 
-        public boolean verifyRequiredAction(String executedAction) {
-            if (failed()) {
                 return false;
             }
 
-            if (!isValidAction(ClientSessionModel.Action.REQUIRED_ACTIONS.name())) return false;
             if (!isActionActive(ClientSessionCode.ActionType.USER)) return false;
 
             final AuthenticationSessionModel authSession = getAuthenticationSession();
 
             if (actionRequest) {
-                String currentRequiredAction = authSession.getAuthNote(AuthenticationManager.CURRENT_REQUIRED_ACTION);
+                String currentRequiredAction = authSession.getAuthNote(AuthenticationProcessor.CURRENT_AUTHENTICATION_EXECUTION);
                 if (executedAction == null || !executedAction.equals(currentRequiredAction)) {
                     logger.debug("required action doesn't match current required action");
-                    authSession.removeAuthNote(AuthenticationManager.CURRENT_REQUIRED_ACTION);
-                    response = redirectToRequiredActions(currentRequiredAction, authSession);
+                    response = redirectToRequiredActions(currentRequiredAction);
                     return false;
                 }
             }
@@ -397,8 +429,33 @@ public class LoginActionsService {
     }
 
 
-    protected Response restartAuthenticationSession(boolean managedRestart) {
-        logger.infof("Login restart requested or authentication session not found. Trying to restart from cookie. Managed restart: %s", managedRestart);
+    public static boolean isFlowTransitionAllowed(String currentFlow, String previousFlow) {
+        if (currentFlow.equals(AUTHENTICATE_PATH) && (previousFlow.equals(REGISTRATION_PATH) || previousFlow.equals(RESET_CREDENTIALS_PATH))) {
+            return true;
+        }
+
+        if (currentFlow.equals(REGISTRATION_PATH) && (previousFlow.equals(AUTHENTICATE_PATH))) {
+            return true;
+        }
+
+        if (currentFlow.equals(RESET_CREDENTIALS_PATH) && (previousFlow.equals(AUTHENTICATE_PATH) || previousFlow.equals(FIRST_BROKER_LOGIN_PATH))) {
+            return true;
+        }
+
+        if (currentFlow.equals(FIRST_BROKER_LOGIN_PATH) && (previousFlow.equals(AUTHENTICATE_PATH) || previousFlow.equals(POST_BROKER_LOGIN_PATH))) {
+            return true;
+        }
+
+        if (currentFlow.equals(POST_BROKER_LOGIN_PATH) && (previousFlow.equals(AUTHENTICATE_PATH) || previousFlow.equals(FIRST_BROKER_LOGIN_PATH))) {
+            return true;
+        }
+
+        return false;
+    }
+
+
+    protected Response restartAuthenticationSessionFromCookie() {
+        logger.info("Authentication session not found. Trying to restart from cookie.");
         AuthenticationSessionModel authSession = null;
         try {
             authSession = RestartLoginCookie.restartSession(session, realm);
@@ -409,17 +466,20 @@ public class LoginActionsService {
         if (authSession != null) {
 
             event.clone();
+            event.detail(Details.RESTART_AFTER_TIMEOUT, "true");
+            event.error(Errors.EXPIRED_CODE);
 
-            String warningMessage = null;
-            if (managedRestart) {
-                event.detail(Details.RESTART_REQUESTED, "true");
-            } else {
-                event.detail(Details.RESTART_AFTER_TIMEOUT, "true");
-                warningMessage = Messages.LOGIN_TIMEOUT;
+            String warningMessage = Messages.LOGIN_TIMEOUT;
+            authSession.setAuthNote(FORWARDED_ERROR_MESSAGE_NOTE, warningMessage);
+
+            String flowPath = authSession.getClientNote(AuthorizationEndpointBase.APP_INITIATED_FLOW);
+            if (flowPath == null) {
+                flowPath = AUTHENTICATE_PATH;
             }
 
-            event.error(Errors.EXPIRED_CODE);
-            return processFlow(false, null, authSession, AUTHENTICATE_PATH, realm.getBrowserFlow(), warningMessage, new AuthenticationProcessor());
+            URI redirectUri = getLastExecutionUrl(flowPath, null);
+            logger.infof("Authentication session restart from cookie succeeded. Redirecting to %s", redirectUri);
+            return Response.status(Response.Status.FOUND).location(redirectUri).build();
         } else {
             event.error(Errors.INVALID_CODE);
             return ErrorPage.error(session, Messages.INVALID_CODE);
@@ -427,29 +487,50 @@ public class LoginActionsService {
     }
 
 
-    protected Response showPageExpired(String flowPath, AuthenticationSessionModel authSession) {
-        String executionId = authSession==null ? null : authSession.getAuthNote(AuthenticationProcessor.LAST_PROCESSED_EXECUTION);
-        String latestFlowPath = authSession==null ? flowPath : authSession.getAuthNote(AuthenticationProcessor.CURRENT_FLOW_PATH);
-        URI lastStepUrl = getLastExecutionUrl(latestFlowPath, executionId);
+    protected Response showPageExpired(AuthenticationSessionModel authSession) {
+        return new PageExpiredRedirect(session, realm, uriInfo)
+                .showPageExpired(authSession);
+    }
 
-        logger.infof("Redirecting to 'page expired' now. Will use URL: %s", lastStepUrl);
 
-        return session.getProvider(LoginFormsProvider.class)
-                .setActionUri(lastStepUrl)
-                .createLoginExpiredPage();
+    protected URI getLastExecutionUrl(String flowPath, String executionId) {
+        return new PageExpiredRedirect(session, realm, uriInfo)
+                .getLastExecutionUrl(flowPath, executionId);
     }
 
 
-    protected URI getLastExecutionUrl(String flowPath, String executionId) {
-        UriBuilder uriBuilder = LoginActionsService.loginActionsBaseUrl(uriInfo)
-                .path(flowPath);
+    /**
+     * protocol independent page for restart of the flow
+     *
+     * @return
+     */
+    @Path(RESTART_PATH)
+    @GET
+    public Response restartSession() {
+        event.event(EventType.RESTART_AUTHENTICATION);
+        SessionCodeChecks checks = new SessionCodeChecks(null, null, null);
+
+        AuthenticationSessionModel authSession = checks.initialVerifyAuthSession();
+        if (authSession == null) {
+            return checks.response;
+        }
 
-        if (executionId != null) {
-            uriBuilder.queryParam("execution", executionId);
+        String flowPath = authSession.getClientNote(AuthorizationEndpointBase.APP_INITIATED_FLOW);
+        if (flowPath == null) {
+            flowPath = AUTHENTICATE_PATH;
         }
-        return uriBuilder.build(realm.getName());
+
+        AuthenticationProcessor.resetFlow(authSession, flowPath);
+
+        // TODO:mposolda Check it's better to put it to AuthenticationProcessor.resetFlow (with consider of brokering)
+        authSession.setAction(CommonClientSessionModel.Action.AUTHENTICATE.name());
+
+        URI redirectUri = getLastExecutionUrl(flowPath, null);
+        logger.infof("Flow restart requested. Redirecting to %s", redirectUri);
+        return Response.status(Response.Status.FOUND).location(redirectUri).build();
     }
 
+
     /**
      * protocol independent login page entry point
      *
@@ -459,13 +540,10 @@ public class LoginActionsService {
     @Path(AUTHENTICATE_PATH)
     @GET
     public Response authenticate(@QueryParam("code") String code,
-                                 @QueryParam("execution") String execution,
-                                 @QueryParam("restart") String restart) {
+                                 @QueryParam("execution") String execution) {
         event.event(EventType.LOGIN);
 
-        boolean wantsSessionRestart = Boolean.parseBoolean(restart);
-
-        SessionCodeChecks checks = checksForCode(code, execution, AUTHENTICATE_PATH, wantsSessionRestart);
+        SessionCodeChecks checks = checksForCode(code, execution, AUTHENTICATE_PATH);
         if (!checks.verifyCode(ClientSessionModel.Action.AUTHENTICATE.name(), ClientSessionCode.ActionType.LOGIN)) {
             return checks.response;
         }
@@ -491,17 +569,31 @@ public class LoginActionsService {
                 .setSession(session)
                 .setUriInfo(uriInfo)
                 .setRequest(request);
-        if (errorMessage != null) processor.setForwardedErrorMessage(new FormMessage(null, errorMessage));
+        if (errorMessage != null) {
+            processor.setForwardedErrorMessage(new FormMessage(null, errorMessage));
+        }
 
+        // Check the forwarded error message, which was set by previous HTTP request
+        String forwardedErrorMessage = authSession.getAuthNote(FORWARDED_ERROR_MESSAGE_NOTE);
+        if (forwardedErrorMessage != null) {
+            authSession.removeAuthNote(FORWARDED_ERROR_MESSAGE_NOTE);
+            processor.setForwardedErrorMessage(new FormMessage(null, forwardedErrorMessage));
+        }
+
+
+        Response response;
         try {
             if (action) {
-                return processor.authenticationAction(execution);
+                response = processor.authenticationAction(execution);
             } else {
-                return processor.authenticate();
+                response = processor.authenticate();
             }
         } catch (Exception e) {
-            return processor.handleBrowserException(e);
+            response = processor.handleBrowserException(e);
+            authSession = processor.getAuthenticationSession(); // Could be changed (eg. Forked flow)
         }
+
+        return BrowserHistoryHelper.getInstance().saveResponseAndRedirect(session, authSession, response, action);
     }
 
     /**
@@ -514,7 +606,7 @@ public class LoginActionsService {
     @POST
     public Response authenticateForm(@QueryParam("code") String code,
                                      @QueryParam("execution") String execution) {
-        return authenticate(code, execution, null);
+        return authenticate(code, execution);
     }
 
     @Path(RESET_CREDENTIALS_PATH)
@@ -538,7 +630,6 @@ public class LoginActionsService {
     /**
      * Verifies that the authentication session has not yet been converted to user session, in other words
      * that the user has not yet completed authentication and logged in.
-     * @param t token
      */
     private class IsAuthenticationSessionNotConvertedToUserSession<T extends JsonWebToken> implements Predicate<T> {
 
@@ -587,13 +678,13 @@ public class LoginActionsService {
 
             if (client == null) {
                 event.error(Errors.CLIENT_NOT_FOUND);
-                session.authenticationSessions().removeAuthenticationSession(realm, authenticationSession);
+                new AuthenticationSessionManager(session).removeAuthenticationSession(realm, authenticationSession, true);
                 throw new LoginActionsServiceException(ErrorPage.error(session, Messages.UNKNOWN_LOGIN_REQUESTER));
             }
 
             if (! client.isEnabled()) {
                 event.error(Errors.CLIENT_NOT_FOUND);
-                session.authenticationSessions().removeAuthenticationSession(realm, authenticationSession);
+                new AuthenticationSessionManager(session).removeAuthenticationSession(realm, authenticationSession, true);
                 throw new LoginActionsServiceException(ErrorPage.error(session, Messages.LOGIN_REQUESTER_NOT_ENABLED));
             }
 
@@ -637,7 +728,7 @@ public class LoginActionsService {
             }
 
             if (authSession == null) { // timeout or logged-already (NOPE - this is handled by IsAuthenticationSessionNotConvertedToUserSession)
-                throw new LoginActionsServiceException(restartAuthenticationSession(false));
+                throw new LoginActionsServiceException(restartAuthenticationSessionFromCookie());
             }
 
             event
@@ -653,7 +744,6 @@ public class LoginActionsService {
     /**
      * This check verifies that if the token has not authentication session set, a new authentication session is introduced
      * for the given client and reset-credentials flow is started with this new session.
-     * @param <T>
      */
     private class ResetCredsIntroduceAuthenticationSessionIfNotSet implements Predicate<ResetCredentialsActionToken> {
 
@@ -702,7 +792,7 @@ public class LoginActionsService {
             
             if (authSession != null && ! Objects.equals(authSession.getAction(), this.expectedAction.name())) {
                 if (ClientSessionModel.Action.REQUIRED_ACTIONS.name().equals(authSession.getAction())) {
-                    throw new LoginActionsServiceException(redirectToRequiredActions(null, authSession));
+                    throw new LoginActionsServiceException(redirectToRequiredActions(null));
                 }
             }
 
@@ -723,12 +813,14 @@ public class LoginActionsService {
     public Response resetCredentialsGET(@QueryParam("code") String code,
                                         @QueryParam("execution") String execution,
                                         @QueryParam(Constants.KEY) String key) {
+        event.event(EventType.RESET_PASSWORD);
+
         if (code != null && key != null) {
             // TODO:mposolda better handling of error
             throw new IllegalStateException("Illegal state");
         }
 
-        AuthenticationSessionModel authSession = session.authenticationSessions().getCurrentAuthenticationSession(realm);
+        AuthenticationSessionModel authSession = new AuthenticationSessionManager(session).getCurrentAuthenticationSession(realm);
 
         // we allow applications to link to reset credentials without going through OAuth or SAML handshakes
         if (authSession == null && key == null && code == null) {
@@ -755,15 +847,15 @@ public class LoginActionsService {
 
         // set up the account service as the endpoint to call.
         ClientModel client = realm.getClientByClientId(clientId);
-        authSession = session.authenticationSessions().createAuthenticationSession(realm, client, true);
+        authSession = new AuthenticationSessionManager(session).createAuthenticationSession(realm, client, true);
         authSession.setAction(ClientSessionModel.Action.AUTHENTICATE.name());
         //authSession.setNote(AuthenticationManager.END_AFTER_REQUIRED_ACTIONS, "true");
         authSession.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL);
-        String redirectUri = Urls.accountBase(uriInfo.getBaseUri()).path("/").build(realm.getName()).toString();
+        String redirectUri = Urls.accountBase(uriInfo.getBaseUri()).path("/").build(realm.getName()).toString(); // TODO:mposolda It seems that this should be taken from client rather then hardcoded to account?
         authSession.setRedirectUri(redirectUri);
-        authSession.setNote(OIDCLoginProtocol.RESPONSE_TYPE_PARAM, OAuth2Constants.CODE);
-        authSession.setNote(OIDCLoginProtocol.REDIRECT_URI_PARAM, redirectUri);
-        authSession.setNote(OIDCLoginProtocol.ISSUER, Urls.realmIssuer(uriInfo.getBaseUri(), realm.getName()));
+        authSession.setClientNote(OIDCLoginProtocol.RESPONSE_TYPE_PARAM, OAuth2Constants.CODE);
+        authSession.setClientNote(OIDCLoginProtocol.REDIRECT_URI_PARAM, redirectUri);
+        authSession.setClientNote(OIDCLoginProtocol.ISSUER, Urls.realmIssuer(uriInfo.getBaseUri(), realm.getName()));
 
         return authSession;
     }
@@ -776,7 +868,7 @@ public class LoginActionsService {
      */
     protected Response resetCredentials(String code, String execution) {
         event.event(EventType.RESET_PASSWORD);
-        SessionCodeChecks checks = checksForCode(code, execution, RESET_CREDENTIALS_PATH, false);
+        SessionCodeChecks checks = checksForCode(code, execution, RESET_CREDENTIALS_PATH);
         if (!checks.verifyCode(ClientSessionModel.Action.AUTHENTICATE.name(), ClientSessionCode.ActionType.USER)) {
             return checks.response;
         }
@@ -835,7 +927,7 @@ public class LoginActionsService {
                   .client(token.getAuthenticationSession().getClient())
                   .error(Errors.EXPIRED_CODE);
                 AuthenticationSessionModel authSession = token.getAuthenticationSession();
-                AuthenticationProcessor.resetFlow(authSession);
+                AuthenticationProcessor.resetFlow(authSession, AUTHENTICATE_PATH);
                 return processAuthentication(false, null, authSession, Messages.LOGIN_TIMEOUT);
             }
 
@@ -866,8 +958,7 @@ public class LoginActionsService {
         if (!isSameBrowser(authSession)) {
             logger.debug("Action request processed in different browser.");
 
-            // TODO:mposolda improve this. The code should be merged with the InfinispanLoginSessionProvider code and rather extracted from the infinispan provider
-            setAuthSessionCookie(authSession.getId());
+            new AuthenticationSessionManager(session).setAuthSessionCookie(authSession.getId(), realm);
 
             authSession.setAuthNote(AuthenticationManager.END_AFTER_REQUIRED_ACTIONS, "true");
         }
@@ -878,7 +969,7 @@ public class LoginActionsService {
 
     // Verify if action is processed in same browser.
     private boolean isSameBrowser(AuthenticationSessionModel actionTokenSession) {
-        String cookieSessionId = session.authenticationSessions().getCurrentAuthenticationSessionId(realm);
+        String cookieSessionId = new AuthenticationSessionManager(session).getCurrentAuthenticationSessionId(realm);
 
         if (cookieSessionId == null) {
             return false;
@@ -901,11 +992,12 @@ public class LoginActionsService {
 
         if (actionTokenSession.getId().equals(parentSessionId)) {
             // It's the correct browser. Let's remove forked session as we won't continue from the login form (browser flow) but from the resetCredentialsByToken flow
-            session.authenticationSessions().removeAuthenticationSession(realm, forkedSession);
+            // Don't expire KC_RESTART cookie at this point
+            new AuthenticationSessionManager(session).removeAuthenticationSession(realm, forkedSession, false);
             logger.infof("Removed forked session: %s", forkedSession.getId());
 
             // Refresh browser cookie
-            setAuthSessionCookie(parentSessionId);
+            new AuthenticationSessionManager(session).setAuthSessionCookie(parentSessionId, realm);
 
             return true;
         } else {
@@ -913,16 +1005,6 @@ public class LoginActionsService {
         }
     }
 
-    // TODO:mposolda improve this. The code should be merged with the InfinispanLoginSessionProvider code and rather extracted from the infinispan provider
-    private void setAuthSessionCookie(String authSessionId) {
-        logger.infof("Set browser cookie to %s", authSessionId);
-
-        String cookiePath = CookieHelper.getRealmCookiePath(realm);
-        boolean sslRequired = realm.getSslRequired().isRequired(session.getContext().getConnection());
-        CookieHelper.addCookie("AUTH_SESSION_ID", authSessionId, cookiePath, null, null, -1, sslRequired, true);
-    }
-
-
 
     protected Response processResetCredentials(boolean actionRequest, String execution, AuthenticationSessionModel authSession, String errorMessage) {
         AuthenticationProcessor authProcessor = new AuthenticationProcessor() {
@@ -937,11 +1019,13 @@ public class LoginActionsService {
                         return ErrorPage.error(session, Messages.IDENTITY_PROVIDER_DIFFERENT_USER_MESSAGE, authenticationSession.getAuthenticatedUser().getUsername(), linkingUser.getUsername());
                     }
 
-                    logger.debugf("Forget-password flow finished when authenticated user '%s' after first broker login.", linkingUser.getUsername());
+                    SerializedBrokeredIdentityContext serializedCtx = SerializedBrokeredIdentityContext.readFromAuthenticationSession(authSession, AbstractIdpAuthenticator.BROKERED_CONTEXT_NOTE);
+                    authSession.setAuthNote(AbstractIdpAuthenticator.FIRST_BROKER_LOGIN_SUCCESS, serializedCtx.getIdentityProviderId());
 
-                    // TODO:mposolda Isn't this a bug that we redirect to 'afterBrokerLoginEndpoint' without rather continue with firstBrokerLogin and other authenticators like OTP?
-                    //return redirectToAfterBrokerLoginEndpoint(authSession, true);
-                    return null;
+                    logger.debugf("Forget-password flow finished when authenticated user '%s' after first broker login with identity provider '%s'.",
+                            linkingUser.getUsername(), serializedCtx.getIdentityProviderId());
+
+                    return redirectToAfterBrokerLoginEndpoint(authSession, true);
                 } else {
                     return super.authenticationComplete();
                 }
@@ -992,7 +1076,7 @@ public class LoginActionsService {
             return ErrorPage.error(session, Messages.REGISTRATION_NOT_ALLOWED);
         }
 
-        SessionCodeChecks checks = checksForCode(code, execution, REGISTRATION_PATH, false);
+        SessionCodeChecks checks = checksForCode(code, execution, REGISTRATION_PATH);
         if (!checks.verifyCode(ClientSessionModel.Action.AUTHENTICATE.name(), ClientSessionCode.ActionType.LOGIN)) {
             return checks.response;
         }
@@ -1008,55 +1092,56 @@ public class LoginActionsService {
         return processRegistration(checks.actionRequest, execution, clientSession, null);
     }
 
-    // TODO:mposolda broker login
-/*
+
     @Path(FIRST_BROKER_LOGIN_PATH)
     @GET
     public Response firstBrokerLoginGet(@QueryParam("code") String code,
                                  @QueryParam("execution") String execution) {
-        return brokerLoginFlow(code, execution, true);
+        return brokerLoginFlow(code, execution, FIRST_BROKER_LOGIN_PATH);
     }
 
     @Path(FIRST_BROKER_LOGIN_PATH)
     @POST
     public Response firstBrokerLoginPost(@QueryParam("code") String code,
                                         @QueryParam("execution") String execution) {
-        return brokerLoginFlow(code, execution, true);
+        return brokerLoginFlow(code, execution, FIRST_BROKER_LOGIN_PATH);
     }
 
     @Path(POST_BROKER_LOGIN_PATH)
     @GET
     public Response postBrokerLoginGet(@QueryParam("code") String code,
                                        @QueryParam("execution") String execution) {
-        return brokerLoginFlow(code, execution, false);
+        return brokerLoginFlow(code, execution, POST_BROKER_LOGIN_PATH);
     }
 
     @Path(POST_BROKER_LOGIN_PATH)
     @POST
     public Response postBrokerLoginPost(@QueryParam("code") String code,
                                         @QueryParam("execution") String execution) {
-        return brokerLoginFlow(code, execution, false);
+        return brokerLoginFlow(code, execution, POST_BROKER_LOGIN_PATH);
     }
 
 
-    protected Response brokerLoginFlow(String code, String execution, final boolean firstBrokerLogin) {
+    protected Response brokerLoginFlow(String code, String execution, String flowPath) {
+        boolean firstBrokerLogin = flowPath.equals(FIRST_BROKER_LOGIN_PATH);
+
         EventType eventType = firstBrokerLogin ? EventType.IDENTITY_PROVIDER_FIRST_LOGIN : EventType.IDENTITY_PROVIDER_POST_LOGIN;
         event.event(eventType);
 
-        SessionCodeChecks checks = checksForCode(code);
+        SessionCodeChecks checks = checksForCode(code, execution, flowPath);
         if (!checks.verifyCode(ClientSessionModel.Action.AUTHENTICATE.name(), ClientSessionCode.ActionType.LOGIN)) {
             return checks.response;
         }
         event.detail(Details.CODE_ID, code);
-        final ClientSessionModel clientSessionn = checks.getClientSession();
+        final AuthenticationSessionModel authSession = checks.getAuthenticationSession();
 
         String noteKey = firstBrokerLogin ? AbstractIdpAuthenticator.BROKERED_CONTEXT_NOTE : PostBrokerLoginConstants.PBL_BROKERED_IDENTITY_CONTEXT;
-        SerializedBrokeredIdentityContext serializedCtx = SerializedBrokeredIdentityContext.readFromClientSession(clientSessionn, noteKey);
+        SerializedBrokeredIdentityContext serializedCtx = SerializedBrokeredIdentityContext.readFromAuthenticationSession(authSession, noteKey);
         if (serializedCtx == null) {
             ServicesLogger.LOGGER.notFoundSerializedCtxInClientSession(noteKey);
             throw new WebApplicationException(ErrorPage.error(session, "Not found serialized context in clientSession."));
         }
-        BrokeredIdentityContext brokerContext = serializedCtx.deserialize(session, clientSessionn);
+        BrokeredIdentityContext brokerContext = serializedCtx.deserialize(session, authSession);
         final String identityProviderAlias = brokerContext.getIdpConfig().getAlias();
 
         String flowId = firstBrokerLogin ? brokerContext.getIdpConfig().getFirstBrokerLoginFlowId() : brokerContext.getIdpConfig().getPostBrokerLoginFlowId();
@@ -1078,23 +1163,24 @@ public class LoginActionsService {
 
             @Override
             protected Response authenticationComplete() {
-                if (!firstBrokerLogin) {
+                if (firstBrokerLogin) {
+                    authSession.setAuthNote(AbstractIdpAuthenticator.FIRST_BROKER_LOGIN_SUCCESS, identityProviderAlias);
+                } else {
                     String authStateNoteKey = PostBrokerLoginConstants.PBL_AUTH_STATE_PREFIX + identityProviderAlias;
-                    clientSessionn.setNote(authStateNoteKey, "true");
+                    authSession.setAuthNote(authStateNoteKey, "true");
                 }
 
-                return redirectToAfterBrokerLoginEndpoint(clientSession, firstBrokerLogin);
+                return redirectToAfterBrokerLoginEndpoint(authSession, firstBrokerLogin);
             }
 
         };
 
-        String flowPath = firstBrokerLogin ? FIRST_BROKER_LOGIN_PATH : POST_BROKER_LOGIN_PATH;
-        return processFlow(execution, clientSessionn, flowPath, brokerLoginFlow, null, processor);
+        return processFlow(checks.actionRequest, execution, authSession, flowPath, brokerLoginFlow, null, processor);
     }
 
-    private Response redirectToAfterBrokerLoginEndpoint(ClientSessionModel clientSession, boolean firstBrokerLogin) {
-        ClientSessionCode accessCode = new ClientSessionCode(session, realm, clientSession);
-        clientSession.setTimestamp(Time.currentTime());
+    private Response redirectToAfterBrokerLoginEndpoint(AuthenticationSessionModel authSession, boolean firstBrokerLogin) {
+        ClientSessionCode<AuthenticationSessionModel> accessCode = new ClientSessionCode<>(session, realm, authSession);
+        authSession.setTimestamp(Time.currentTime());
 
         URI redirect = firstBrokerLogin ? Urls.identityProviderAfterFirstBrokerLogin(uriInfo.getBaseUri(), realm.getName(), accessCode.getCode()) :
                 Urls.identityProviderAfterPostBrokerLogin(uriInfo.getBaseUri(), realm.getName(), accessCode.getCode()) ;
@@ -1102,7 +1188,7 @@ public class LoginActionsService {
 
         return Response.status(302).location(redirect).build();
     }
-*/
+
 
     /**
      * OAuth grant page.  You should not invoked this directly!
@@ -1116,7 +1202,7 @@ public class LoginActionsService {
     public Response processConsent(final MultivaluedMap<String, String> formData) {
         event.event(EventType.LOGIN);
         String code = formData.getFirst("code");
-        SessionCodeChecks checks = checksForCode(code, null, REQUIRED_ACTION, false);
+        SessionCodeChecks checks = checksForCode(code, null, REQUIRED_ACTION);
         if (!checks.verifyRequiredAction(ClientSessionModel.Action.OAUTH_GRANT.name())) {
             return checks.response;
         }
@@ -1159,7 +1245,6 @@ public class LoginActionsService {
         event.detail(Details.CONSENT, Details.CONSENT_VALUE_CONSENT_GRANTED);
         event.success();
 
-        // TODO:mposolda So assume that requiredActions were already done in this stage. Doublecheck...
         AuthenticatedClientSessionModel clientSession = AuthenticationProcessor.attachSession(authSession, null, session, realm, clientConnection, event);
         return AuthenticationManager.redirectAfterSuccessfulFlow(session, realm, clientSession.getUserSession(), clientSession, request, uriInfo, clientConnection, event, authSession.getProtocol());
     }
@@ -1276,27 +1361,12 @@ public class LoginActionsService {
         return null;
     }
 
-    private String getActionCookie() {
-        return getActionCookie(headers, realm, uriInfo, clientConnection);
-    }
-
-    public static String getActionCookie(HttpHeaders headers, RealmModel realm, UriInfo uriInfo, ClientConnection clientConnection) {
-        Cookie cookie = headers.getCookies().get(ACTION_COOKIE);
-        AuthenticationManager.expireCookie(realm, ACTION_COOKIE, AuthenticationManager.getRealmCookiePath(realm, uriInfo), realm.getSslRequired().isRequired(clientConnection), clientConnection);
-        return cookie != null ? cookie.getValue() : null;
-    }
-
-    // TODO: Remove this method. We will be able to use login-session-cookie
-    public static void createActionCookie(RealmModel realm, UriInfo uriInfo, ClientConnection clientConnection, String sessionId) {
-        CookieHelper.addCookie(ACTION_COOKIE, sessionId, AuthenticationManager.getRealmCookiePath(realm, uriInfo), null, null, -1, realm.getSslRequired().isRequired(clientConnection), true);
-    }
-
     private void initLoginEvent(AuthenticationSessionModel authSession) {
-        String responseType = authSession.getNote(OIDCLoginProtocol.RESPONSE_TYPE_PARAM);
+        String responseType = authSession.getClientNote(OIDCLoginProtocol.RESPONSE_TYPE_PARAM);
         if (responseType == null) {
             responseType = "code";
         }
-        String respMode = authSession.getNote(OIDCLoginProtocol.RESPONSE_MODE_PARAM);
+        String respMode = authSession.getClientNote(OIDCLoginProtocol.RESPONSE_MODE_PARAM);
         OIDCResponseMode responseMode = OIDCResponseMode.parse(respMode, OIDCResponseType.parse(responseType));
 
         event.event(EventType.LOGIN).client(authSession.getClient())
@@ -1345,7 +1415,9 @@ public class LoginActionsService {
     }
 
     private Response processRequireAction(final String code, String action) {
-        SessionCodeChecks checks = checksForCode(code, action, REQUIRED_ACTION, false);
+        event.event(EventType.CUSTOM_REQUIRED_ACTION);
+
+        SessionCodeChecks checks = checksForCode(code, action, REQUIRED_ACTION);
         if (!checks.verifyRequiredAction(action)) {
             return checks.response;
         }
@@ -1375,21 +1447,24 @@ public class LoginActionsService {
                 throw new RuntimeException("Cannot call ignore within processAction()");
             }
         };
+
+        Response response;
         provider.processAction(context);
+
+        authSession.setAuthNote(AuthenticationProcessor.LAST_PROCESSED_EXECUTION, action);
+
         if (context.getStatus() == RequiredActionContext.Status.SUCCESS) {
             event.clone().success();
             initLoginEvent(authSession);
             event.event(EventType.LOGIN);
             authSession.removeRequiredAction(factory.getId());
             authSession.getAuthenticatedUser().removeRequiredAction(factory.getId());
-            authSession.removeAuthNote(AuthenticationManager.CURRENT_REQUIRED_ACTION);
+            authSession.removeAuthNote(AuthenticationProcessor.CURRENT_AUTHENTICATION_EXECUTION);
 
-            return redirectToRequiredActions(action, authSession);
-        }
-        if (context.getStatus() == RequiredActionContext.Status.CHALLENGE) {
-            return context.getChallenge();
-        }
-        if (context.getStatus() == RequiredActionContext.Status.FAILURE) {
+            response = AuthenticationManager.nextActionAfterAuthentication(session, authSession, clientConnection, request, uriInfo, event);
+        } else if (context.getStatus() == RequiredActionContext.Status.CHALLENGE) {
+            response = context.getChallenge();
+        } else if (context.getStatus() == RequiredActionContext.Status.FAILURE) {
             LoginProtocol protocol = context.getSession().getProvider(LoginProtocol.class, authSession.getProtocol());
             protocol.setRealm(context.getRealm())
                     .setHttpHeaders(context.getHttpRequest().getHttpHeaders())
@@ -1397,18 +1472,16 @@ public class LoginActionsService {
                     .setEventBuilder(event);
 
             event.detail(Details.CUSTOM_REQUIRED_ACTION, action);
-            Response response = protocol.sendError(authSession, Error.CONSENT_DENIED);
+            response = protocol.sendError(authSession, Error.CONSENT_DENIED);
             event.error(Errors.REJECTED_BY_USER);
-            return response;
-
+        } else {
+            throw new RuntimeException("Unreachable");
         }
 
-        throw new RuntimeException("Unreachable");
+        return BrowserHistoryHelper.getInstance().saveResponseAndRedirect(session, authSession, response, true);
     }
 
-    private Response redirectToRequiredActions(String action, AuthenticationSessionModel authSession) {
-        authSession.setAuthNote(AuthenticationProcessor.LAST_PROCESSED_EXECUTION, action);
-
+    private Response redirectToRequiredActions(String action) {
         UriBuilder uriBuilder = LoginActionsService.loginActionsBaseUrl(uriInfo)
                 .path(LoginActionsService.REQUIRED_ACTION);
 
diff --git a/services/src/main/java/org/keycloak/services/resources/RealmsResource.java b/services/src/main/java/org/keycloak/services/resources/RealmsResource.java
index ab99d5d..bb8de2d 100755
--- a/services/src/main/java/org/keycloak/services/resources/RealmsResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/RealmsResource.java
@@ -234,16 +234,12 @@ public class RealmsResource {
     public IdentityBrokerService getBrokerService(final @PathParam("realm") String name) {
         RealmModel realm = init(name);
 
-        // TODO:mposolda
-        /*
         IdentityBrokerService brokerService = new IdentityBrokerService(realm);
         ResteasyProviderFactory.getInstance().injectProperties(brokerService);
 
         brokerService.init();
 
         return brokerService;
-        */
-        return null;
     }
 
     @OPTIONS
diff --git a/services/src/main/java/org/keycloak/services/Urls.java b/services/src/main/java/org/keycloak/services/Urls.java
index 8735cc8..b86c04f 100755
--- a/services/src/main/java/org/keycloak/services/Urls.java
+++ b/services/src/main/java/org/keycloak/services/Urls.java
@@ -207,8 +207,7 @@ public class Urls {
     }
 
     public static URI realmLoginRestartPage(URI baseUri, String realmId) {
-        return loginActionsBase(baseUri).path(LoginActionsService.class, "authenticate")
-                .queryParam("restart", "true")
+        return loginActionsBase(baseUri).path(LoginActionsService.class, "restartSession")
                 .build(realmId);
     }
 
diff --git a/services/src/main/java/org/keycloak/services/util/BrowserHistoryHelper.java b/services/src/main/java/org/keycloak/services/util/BrowserHistoryHelper.java
new file mode 100644
index 0000000..89cda2c
--- /dev/null
+++ b/services/src/main/java/org/keycloak/services/util/BrowserHistoryHelper.java
@@ -0,0 +1,191 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.services.util;
+
+import java.net.URI;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import javax.ws.rs.core.Response;
+
+import org.jboss.logging.Logger;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.sessions.AuthenticationSessionModel;
+import org.keycloak.theme.BrowserSecurityHeaderSetup;
+import org.keycloak.utils.MediaType;
+
+/**
+ * The point of this is to improve experience of browser history (back/forward/refresh buttons), but ensure there is no more redirects then necessary.
+ *
+ * Ideally we want to:
+ * - Remove all POST requests from browser history, because browsers don't automatically re-send them when click "back" button. POSTS in history causes unfriendly dialogs and browser "Page is expired" pages.
+ *
+ * - Keep the browser URL to match the flow and execution from authentication session. This means that browser refresh works fine and show us the correct form.
+ *
+ * - Avoid redirects. This is possible with javascript based approach (JavascriptHistoryReplace). The RedirectAfterPostHelper requires one redirect after POST, but works even on browser without javascript and
+ * on old browsers where "history.replaceState" is unsupported.
+ *
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public abstract class BrowserHistoryHelper {
+
+    protected static final Logger logger = Logger.getLogger(BrowserHistoryHelper.class);
+
+    public abstract Response saveResponseAndRedirect(KeycloakSession session, AuthenticationSessionModel authSession, Response response, boolean actionRequest);
+
+    public abstract Response loadSavedResponse(KeycloakSession session, AuthenticationSessionModel authSession);
+
+
+    // Always rely on javascript for now
+    public static BrowserHistoryHelper getInstance() {
+        return new JavascriptHistoryReplace();
+        //return new RedirectAfterPostHelper();
+        //return new NoOpHelper();
+    }
+
+
+    // IMPL
+
+    private static class JavascriptHistoryReplace extends BrowserHistoryHelper {
+
+        private static final Pattern HEAD_END_PATTERN = Pattern.compile("</[hH][eE][aA][dD]>");
+
+        @Override
+        public Response saveResponseAndRedirect(KeycloakSession session, AuthenticationSessionModel authSession, Response response, boolean actionRequest) {
+            if (!actionRequest) {
+                return response;
+            }
+
+            // For now, handle just status 200 with String body. See if more is needed...
+            if (response.getStatus() == 200) {
+                Object entity = response.getEntity();
+                if (entity instanceof String) {
+                    String responseString = (String) entity;
+
+                    URI lastExecutionURL = new PageExpiredRedirect(session, session.getContext().getRealm(), session.getContext().getUri()).getLastExecutionUrl(authSession);
+
+                    // Inject javascript for history "replaceState"
+                    String responseWithJavascript = responseWithJavascript(responseString, lastExecutionURL.toString());
+
+                    return Response.fromResponse(response).entity(responseWithJavascript).build();
+                }
+            }
+
+            return response;
+        }
+
+        @Override
+        public Response loadSavedResponse(KeycloakSession session, AuthenticationSessionModel authSession) {
+            return null;
+        }
+
+
+        private String responseWithJavascript(String origHtml, String lastExecutionUrl) {
+            Matcher m = HEAD_END_PATTERN.matcher(origHtml);
+
+            if (m.find()) {
+                int start = m.start();
+
+                String javascript = getJavascriptText(lastExecutionUrl);
+
+                return new StringBuilder(origHtml.substring(0, start))
+                        .append(javascript )
+                        .append(origHtml.substring(start))
+                        .toString();
+            } else {
+                return origHtml;
+            }
+        }
+
+        private String getJavascriptText(String lastExecutionUrl) {
+            return new StringBuilder("<SCRIPT>")
+                    .append(" if (typeof history.replaceState === 'function') {")
+                    .append("  history.replaceState({}, \"some title\", \"" + lastExecutionUrl + "\");")
+                    .append(" }")
+                    .append("</SCRIPT>")
+                    .toString();
+        }
+
+    }
+
+
+    private static class RedirectAfterPostHelper extends BrowserHistoryHelper {
+
+        private static final String CACHED_RESPONSE = "cached.response";
+
+        @Override
+        public Response saveResponseAndRedirect(KeycloakSession session, AuthenticationSessionModel authSession, Response response, boolean actionRequest) {
+            if (!actionRequest) {
+                return response;
+            }
+
+            // For now, handle just status 200 with String body. See if more is needed...
+            if (response.getStatus() == 200) {
+                Object entity = response.getEntity();
+                if (entity instanceof String) {
+                    String responseString = (String) entity;
+                    authSession.setAuthNote(CACHED_RESPONSE, responseString);
+
+                    URI lastExecutionURL = new PageExpiredRedirect(session, session.getContext().getRealm(), session.getContext().getUri()).getLastExecutionUrl(authSession);
+
+                    // TODO:mposolda trace
+                    logger.infof("Saved response challenge and redirect to %s", lastExecutionURL);
+
+                    return Response.status(302).location(lastExecutionURL).build();
+                }
+            }
+
+            return response;
+        }
+
+
+        @Override
+        public Response loadSavedResponse(KeycloakSession session, AuthenticationSessionModel authSession) {
+            String savedResponse = authSession.getAuthNote(CACHED_RESPONSE);
+            if (savedResponse != null) {
+                authSession.removeAuthNote(CACHED_RESPONSE);
+
+                // TODO:mposolda trace
+                logger.infof("Restored previously saved request");
+
+                Response.ResponseBuilder builder = Response.status(200).type(MediaType.TEXT_HTML_UTF_8).entity(savedResponse);
+                BrowserSecurityHeaderSetup.headers(builder, session.getContext().getRealm()); // TODO:mposolda cRather all the headers from the saved response should be added here.
+                return builder.build();
+            }
+
+            return null;
+        }
+
+    }
+
+
+    private static class NoOpHelper extends BrowserHistoryHelper {
+
+        @Override
+        public Response saveResponseAndRedirect(KeycloakSession session, AuthenticationSessionModel authSession, Response response, boolean actionRequest) {
+            return response;
+        }
+
+
+        @Override
+        public Response loadSavedResponse(KeycloakSession session, AuthenticationSessionModel authSession) {
+            return null;
+        }
+
+    }
+}
diff --git a/services/src/main/java/org/keycloak/services/util/PageExpiredRedirect.java b/services/src/main/java/org/keycloak/services/util/PageExpiredRedirect.java
new file mode 100644
index 0000000..83f0ce5
--- /dev/null
+++ b/services/src/main/java/org/keycloak/services/util/PageExpiredRedirect.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.services.util;
+
+import java.net.URI;
+
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriBuilder;
+import javax.ws.rs.core.UriInfo;
+
+import org.jboss.logging.Logger;
+import org.keycloak.authentication.AuthenticationProcessor;
+import org.keycloak.forms.login.LoginFormsProvider;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.protocol.AuthorizationEndpointBase;
+import org.keycloak.services.resources.LoginActionsService;
+import org.keycloak.sessions.AuthenticationSessionModel;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class PageExpiredRedirect {
+
+    protected static final Logger logger = Logger.getLogger(PageExpiredRedirect.class);
+
+    private final KeycloakSession session;
+    private final RealmModel realm;
+    private final UriInfo uriInfo;
+
+    public PageExpiredRedirect(KeycloakSession session, RealmModel realm, UriInfo uriInfo) {
+        this.session = session;
+        this.realm = realm;
+        this.uriInfo = uriInfo;
+    }
+
+
+    public Response showPageExpired(AuthenticationSessionModel authSession) {
+        URI lastStepUrl = getLastExecutionUrl(authSession);
+
+        logger.infof("Redirecting to 'page expired' now. Will use last step URL: %s", lastStepUrl);
+
+        return session.getProvider(LoginFormsProvider.class)
+                .setActionUri(lastStepUrl)
+                .createLoginExpiredPage();
+    }
+
+
+    public URI getLastExecutionUrl(String flowPath, String executionId) {
+        UriBuilder uriBuilder = LoginActionsService.loginActionsBaseUrl(uriInfo)
+                .path(flowPath);
+
+        if (executionId != null) {
+            uriBuilder.queryParam("execution", executionId);
+        }
+        return uriBuilder.build(realm.getName());
+    }
+
+
+    public URI getLastExecutionUrl(AuthenticationSessionModel authSession) {
+        String executionId = authSession.getAuthNote(AuthenticationProcessor.CURRENT_AUTHENTICATION_EXECUTION);
+        String latestFlowPath = authSession.getAuthNote(AuthenticationProcessor.CURRENT_FLOW_PATH);
+
+        if (latestFlowPath == null) {
+            latestFlowPath = authSession.getClientNote(AuthorizationEndpointBase.APP_INITIATED_FLOW);
+        }
+
+        if (latestFlowPath == null) {
+            latestFlowPath = LoginActionsService.AUTHENTICATE_PATH;
+        }
+
+        return getLastExecutionUrl(latestFlowPath, executionId);
+    }
+
+}
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 f81abc9..a8c42b1 100755
--- a/services/src/main/java/org/keycloak/social/twitter/TwitterIdentityProvider.java
+++ b/services/src/main/java/org/keycloak/social/twitter/TwitterIdentityProvider.java
@@ -26,13 +26,13 @@ import org.keycloak.broker.social.SocialIdentityProvider;
 import org.keycloak.common.ClientConnection;
 import org.keycloak.events.EventBuilder;
 import org.keycloak.events.EventType;
-import org.keycloak.models.ClientSessionModel;
 import org.keycloak.models.FederatedIdentityModel;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.RealmModel;
 import org.keycloak.services.ErrorPage;
 import org.keycloak.services.managers.ClientSessionCode;
 import org.keycloak.services.messages.Messages;
+import org.keycloak.sessions.AuthenticationSessionModel;
 import twitter4j.Twitter;
 import twitter4j.TwitterFactory;
 import twitter4j.auth.AccessToken;
@@ -40,6 +40,7 @@ import twitter4j.auth.RequestToken;
 
 import javax.ws.rs.GET;
 import javax.ws.rs.QueryParam;
+import javax.ws.rs.WebApplicationException;
 import javax.ws.rs.core.Context;
 import javax.ws.rs.core.HttpHeaders;
 import javax.ws.rs.core.MediaType;
@@ -54,6 +55,10 @@ public class TwitterIdentityProvider extends AbstractIdentityProvider<OAuth2Iden
         SocialIdentityProvider<OAuth2IdentityProviderConfig> {
 
     protected static final Logger logger = Logger.getLogger(TwitterIdentityProvider.class);
+
+    private static final String TWITTER_TOKEN = "twitter_token";
+    private static final String TWITTER_TOKENSECRET = "twitter_tokenSecret";
+
     public TwitterIdentityProvider(KeycloakSession session, OAuth2IdentityProviderConfig config) {
         super(session, config);
     }
@@ -72,10 +77,10 @@ public class TwitterIdentityProvider extends AbstractIdentityProvider<OAuth2Iden
             URI uri = new URI(request.getRedirectUri() + "?state=" + request.getState());
 
             RequestToken requestToken = twitter.getOAuthRequestToken(uri.toString());
-            ClientSessionModel clientSession = request.getClientSession();
+            AuthenticationSessionModel authSession = request.getAuthenticationSession();
 
-            clientSession.setNote("twitter_token", requestToken.getToken());
-            clientSession.setNote("twitter_tokenSecret", requestToken.getTokenSecret());
+            authSession.setAuthNote(TWITTER_TOKEN, requestToken.getToken());
+            authSession.setAuthNote(TWITTER_TOKENSECRET, requestToken.getTokenSecret());
 
             URI authenticationUrl = URI.create(requestToken.getAuthenticationURL());
 
@@ -115,16 +120,14 @@ public class TwitterIdentityProvider extends AbstractIdentityProvider<OAuth2Iden
             }
 
             try {
-                // TODO:mposolda
-                /*
                 Twitter twitter = new TwitterFactory().getInstance();
 
                 twitter.setOAuthConsumer(getConfig().getClientId(), getConfig().getClientSecret());
 
-                ClientSessionModel clientSession = ClientSessionCode.getClientSession(state, session, realm);
+                AuthenticationSessionModel authSession = ClientSessionCode.getClientSession(state, session, realm, AuthenticationSessionModel.class);
 
-                String twitterToken = clientSession.getNote("twitter_token");
-                String twitterSecret = clientSession.getNote("twitter_tokenSecret");
+                String twitterToken = authSession.getAuthNote(TWITTER_TOKEN);
+                String twitterSecret = authSession.getAuthNote(TWITTER_TOKENSECRET);
 
                 RequestToken requestToken = new RequestToken(twitterToken, twitterSecret);
 
@@ -151,8 +154,8 @@ public class TwitterIdentityProvider extends AbstractIdentityProvider<OAuth2Iden
                 identity.setCode(state);
 
                 return callback.authenticated(identity);
-                */
-                return null;
+            } catch (WebApplicationException e) {
+                return e.getResponse();
             } catch (Exception e) {
                 logger.error("Could get user profile from twitter.", e);
             }
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractKeycloakIdentityProviderTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractKeycloakIdentityProviderTest.java
index b047595..14c98dd 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractKeycloakIdentityProviderTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractKeycloakIdentityProviderTest.java
@@ -69,11 +69,13 @@ public abstract class AbstractKeycloakIdentityProviderTest extends AbstractIdent
     }
 
     @Test
-    public void testDisabledUser() {
+    public void testDisabledUser() throws Exception {
         KeycloakSession session = brokerServerRule.startSession();
         setUpdateProfileFirstLogin(session.realms().getRealmByName("realm-with-broker"), IdentityProviderRepresentation.UPFLM_OFF);
         brokerServerRule.stopSession(session, true);
 
+        Thread.sleep(10000000);
+
         driver.navigate().to("http://localhost:8081/test-app");
         loginPage.clickSocial(getProviderId());
         loginPage.login("test-user", "password");
@@ -328,7 +330,7 @@ public abstract class AbstractKeycloakIdentityProviderTest extends AbstractIdent
     }
 
     @Test
-    public void testSuccessfulAuthenticationWithoutUpdateProfile_newUser_emailAsUsername() {
+    public void testSuccessfulAuthenticationWithoutUpdateProfile_newUser_emailAsUsername() throws Exception {
         RealmModel realm = getRealm();
         realm.setRegistrationEmailAsUsername(true);
         setUpdateProfileFirstLogin(realm, IdentityProviderRepresentation.UPFLM_OFF);
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/OIDCKeyCloakServerBrokerBasicTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/OIDCKeyCloakServerBrokerBasicTest.java
index 58c4ca1..c8e9d9b 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/OIDCKeyCloakServerBrokerBasicTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/OIDCKeyCloakServerBrokerBasicTest.java
@@ -116,7 +116,7 @@ public class OIDCKeyCloakServerBrokerBasicTest extends AbstractKeycloakIdentityP
     }
 
     @Test
-    public void testDisabledUser() {
+    public void testDisabledUser() throws Exception {
         super.testDisabledUser();
     }
 
@@ -156,7 +156,7 @@ public class OIDCKeyCloakServerBrokerBasicTest extends AbstractKeycloakIdentityP
     }
 
     @Test
-    public void testSuccessfulAuthenticationWithoutUpdateProfile_newUser_emailAsUsername() {
+    public void testSuccessfulAuthenticationWithoutUpdateProfile_newUser_emailAsUsername() throws Exception {
         super.testSuccessfulAuthenticationWithoutUpdateProfile_newUser_emailAsUsername();
     }
 
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/OIDCKeycloakServerBrokerWithConsentTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/OIDCKeycloakServerBrokerWithConsentTest.java
index 078666f..b576cbf 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/OIDCKeycloakServerBrokerWithConsentTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/OIDCKeycloakServerBrokerWithConsentTest.java
@@ -143,6 +143,7 @@ public class OIDCKeycloakServerBrokerWithConsentTest extends AbstractIdentityPro
             this.session = brokerServerRule.startSession();
 
             session.sessions().removeExpired(getRealm());
+            session.authenticationSessions().removeExpired(getRealm());
 
             brokerServerRule.stopSession(this.session, true);
             this.session = brokerServerRule.startSession();
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/SAMLKeyCloakServerBrokerBasicTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/SAMLKeyCloakServerBrokerBasicTest.java
index 3b83f03..8afc49b 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/SAMLKeyCloakServerBrokerBasicTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/SAMLKeyCloakServerBrokerBasicTest.java
@@ -128,7 +128,7 @@ public class SAMLKeyCloakServerBrokerBasicTest extends AbstractKeycloakIdentityP
     }
 
     @Test
-    public void testSuccessfulAuthenticationWithoutUpdateProfile_newUser_emailAsUsername() {
+    public void testSuccessfulAuthenticationWithoutUpdateProfile_newUser_emailAsUsername() throws Exception {
         super.testSuccessfulAuthenticationWithoutUpdateProfile_newUser_emailAsUsername();
     }
 
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/AuthenticationSessionProviderTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/AuthenticationSessionProviderTest.java
index e747683..8ee397a 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/AuthenticationSessionProviderTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/AuthenticationSessionProviderTest.java
@@ -28,6 +28,7 @@ import org.keycloak.models.RealmModel;
 import org.keycloak.models.UserManager;
 import org.keycloak.models.UserModel;
 import org.keycloak.sessions.AuthenticationSessionModel;
+import org.keycloak.sessions.CommonClientSessionModel;
 import org.keycloak.testsuite.rule.KeycloakRule;
 
 /**
@@ -77,7 +78,7 @@ public class AuthenticationSessionProviderTest {
         ClientModel client1 = realm.getClientByClientId("test-app");
         UserModel user1 = session.users().getUserByUsername("user1", realm);
 
-        AuthenticationSessionModel authSession = session.authenticationSessions().createAuthenticationSession(realm, client1, false);
+        AuthenticationSessionModel authSession = session.authenticationSessions().createAuthenticationSession(realm, client1);
 
         authSession.setAction("foo");
         authSession.setTimestamp(100);
@@ -86,7 +87,8 @@ public class AuthenticationSessionProviderTest {
 
         // Ensure session is here
         authSession = session.authenticationSessions().getAuthenticationSession(realm, authSession.getId());
-        testLoginSession(authSession, client1.getId(), null, "foo", 100);
+        testLoginSession(authSession, client1.getId(), null, "foo");
+        Assert.assertEquals(100, authSession.getTimestamp());
 
         // Update and commit
         authSession.setAction("foo-updated");
@@ -97,7 +99,8 @@ public class AuthenticationSessionProviderTest {
 
         // Ensure session was updated
         authSession = session.authenticationSessions().getAuthenticationSession(realm, authSession.getId());
-        testLoginSession(authSession, client1.getId(), user1.getId(), "foo-updated", 200);
+        testLoginSession(authSession, client1.getId(), user1.getId(), "foo-updated");
+        Assert.assertEquals(200, authSession.getTimestamp());
 
         // Remove and commit
         session.authenticationSessions().removeAuthenticationSession(realm, authSession);
@@ -109,14 +112,52 @@ public class AuthenticationSessionProviderTest {
 
     }
 
-    private void testLoginSession(AuthenticationSessionModel authSession, String expectedClientId, String expectedUserId, String expectedAction, int expectedTimestamp) {
+    @Test
+    public void testLoginSessionRestart() {
+        ClientModel client1 = realm.getClientByClientId("test-app");
+        UserModel user1 = session.users().getUserByUsername("user1", realm);
+
+        AuthenticationSessionModel authSession = session.authenticationSessions().createAuthenticationSession(realm, client1);
+
+        authSession.setAction("foo");
+        authSession.setTimestamp(100);
+
+        authSession.setAuthenticatedUser(user1);
+        authSession.setAuthNote("foo", "bar");
+        authSession.setClientNote("foo2", "bar2");
+        authSession.setExecutionStatus("123", CommonClientSessionModel.ExecutionStatus.SUCCESS);
+
+        resetSession();
+
+        client1 = realm.getClientByClientId("test-app");
+        authSession = session.authenticationSessions().getAuthenticationSession(realm, authSession.getId());
+        authSession.restartSession(realm, client1);
+
+        resetSession();
+
+        authSession = session.authenticationSessions().getAuthenticationSession(realm, authSession.getId());
+        testLoginSession(authSession, client1.getId(), null, null);
+        Assert.assertTrue(authSession.getTimestamp() > 0);
+
+        Assert.assertTrue(authSession.getClientNotes().isEmpty());
+        Assert.assertNull(authSession.getAuthNote("foo2"));
+        Assert.assertTrue(authSession.getExecutionStatus().isEmpty());
+
+    }
+
+    private void testLoginSession(AuthenticationSessionModel authSession, String expectedClientId, String expectedUserId, String expectedAction) {
         Assert.assertEquals(expectedClientId, authSession.getClient().getId());
+
         if (expectedUserId == null) {
             Assert.assertNull(authSession.getAuthenticatedUser());
         } else {
             Assert.assertEquals(expectedUserId, authSession.getAuthenticatedUser().getId());
         }
-        Assert.assertEquals(expectedAction, authSession.getAction());
-        Assert.assertEquals(expectedTimestamp, authSession.getTimestamp());
+
+        if (expectedAction == null) {
+            Assert.assertNull(authSession.getAction());
+        } else {
+            Assert.assertEquals(expectedAction, authSession.getAction());
+        }
     }
 }
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/CacheTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/CacheTest.java
index abbf912..68746c0 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/CacheTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/CacheTest.java
@@ -103,7 +103,7 @@ public class CacheTest {
             user.setFirstName("firstName");
             user.addRequiredAction(UserModel.RequiredAction.CONFIGURE_TOTP);
 
-            UserSessionModel userSession = session.sessions().createUserSession(realm, user, "testAddUserNotAddedToCache", "127.0.0.1", "auth", false, null, null);
+            UserSessionModel userSession = session.sessions().createUserSession("123", realm, user, "testAddUserNotAddedToCache", "127.0.0.1", "auth", false, null, null);
             UserModel user2 = userSession.getUser();
 
             user.setLastName("lastName");
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/ClusterSessionCleanerTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/ClusterSessionCleanerTest.java
index 7b6de1b..66894f6 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/ClusterSessionCleanerTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/ClusterSessionCleanerTest.java
@@ -75,7 +75,7 @@ public class ClusterSessionCleanerTest {
         RealmModel realm1 = session1.realms().getRealmByName(REALM_NAME);
         UserModel user1 = session1.users().getUserByUsername("test-user@localhost", realm1);
         for (int i=0 ; i<15 ; i++) {
-            session1.sessions().createUserSession(realm1, user1, user1.getUsername(), "127.0.0.1", "form", true, null, null);
+            session1.sessions().createUserSession("123", realm1, user1, user1.getUsername(), "127.0.0.1", "form", true, null, null);
         }
         session1 = commit(server1, session1);
 
@@ -87,7 +87,7 @@ public class ClusterSessionCleanerTest {
         Assert.assertEquals(user2.getId(), user1.getId());
 
         for (int i=0 ; i<15 ; i++) {
-            session2.sessions().createUserSession(realm2, user2, user2.getUsername(), "127.0.0.1", "form", true, null, null);
+            session2.sessions().createUserSession("456", realm2, user2, user2.getUsername(), "127.0.0.1", "form", true, null, null);
         }
         session2 = commit(server2, session2);
 
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserSessionProviderTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserSessionProviderTest.java
index 4e13b30..683a490 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserSessionProviderTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserSessionProviderTest.java
@@ -31,6 +31,7 @@ import org.keycloak.models.RealmModel;
 import org.keycloak.models.UserLoginFailureModel;
 import org.keycloak.models.UserModel;
 import org.keycloak.models.UserSessionModel;
+import org.keycloak.models.utils.KeycloakModelUtils;
 import org.keycloak.protocol.oidc.OIDCLoginProtocol;
 import org.keycloak.models.UserManager;
 import org.keycloak.testsuite.rule.KeycloakRule;
@@ -283,11 +284,11 @@ public class UserSessionProviderTest {
             Set<String> expiredClientSessions = new HashSet<String>();
 
             Time.setOffset(-(realm.getSsoSessionMaxLifespan() + 1));
-            expired.add(session.sessions().createUserSession(realm, session.users().getUserByUsername("user1", realm), "user1", "127.0.0.1", "form", true, null, null).getId());
+            expired.add(session.sessions().createUserSession(KeycloakModelUtils.generateId(), realm, session.users().getUserByUsername("user1", realm), "user1", "127.0.0.1", "form", true, null, null).getId());
             expiredClientSessions.add(session.sessions().createClientSession(realm, client).getId());
 
             Time.setOffset(0);
-            UserSessionModel s = session.sessions().createUserSession(realm, session.users().getUserByUsername("user2", realm), "user2", "127.0.0.1", "form", true, null, null);
+            UserSessionModel s = session.sessions().createUserSession(KeycloakModelUtils.generateId(), realm, session.users().getUserByUsername("user2", realm), "user2", "127.0.0.1", "form", true, null, null);
             //s.setLastSessionRefresh(Time.currentTime() - (realm.getSsoSessionIdleTimeout() + 1));
             s.setLastSessionRefresh(0);
             expired.add(s.getId());
@@ -299,7 +300,7 @@ public class UserSessionProviderTest {
             Set<String> valid = new HashSet<String>();
             Set<String> validClientSessions = new HashSet<String>();
 
-            valid.add(session.sessions().createUserSession(realm, session.users().getUserByUsername("user1", realm), "user1", "127.0.0.1", "form", true, null, null).getId());
+            valid.add(session.sessions().createUserSession(KeycloakModelUtils.generateId(), realm, session.users().getUserByUsername("user1", realm), "user1", "127.0.0.1", "form", true, null, null).getId());
             validClientSessions.add(session.sessions().createClientSession(realm, client).getId());
 
             resetSession();
@@ -427,7 +428,7 @@ public class UserSessionProviderTest {
         try {
             for (int i = 0; i < 25; i++) {
                 Time.setOffset(i);
-                UserSessionModel userSession = session.sessions().createUserSession(realm, session.users().getUserByUsername("user1", realm), "user1", "127.0.0." + i, "form", false, null, null);
+                UserSessionModel userSession = session.sessions().createUserSession(KeycloakModelUtils.generateId(), realm, session.users().getUserByUsername("user1", realm), "user1", "127.0.0." + i, "form", false, null, null);
                 ClientSessionModel clientSession = session.sessions().createClientSession(realm, realm.getClientByClientId("test-app"));
                 clientSession.setUserSession(userSession);
                 clientSession.setRedirectUri("http://redirect");
@@ -450,7 +451,7 @@ public class UserSessionProviderTest {
 
     @Test
     public void testCreateAndGetInSameTransaction() {
-        UserSessionModel userSession = session.sessions().createUserSession(realm, session.users().getUserByUsername("user1", realm), "user1", "127.0.0.2", "form", true, null, null);
+        UserSessionModel userSession = session.sessions().createUserSession(KeycloakModelUtils.generateId(), realm, session.users().getUserByUsername("user1", realm), "user1", "127.0.0.2", "form", true, null, null);
         ClientSessionModel clientSession = createClientSession(realm.getClientByClientId("test-app"), userSession, "http://redirect", "state", new HashSet<String>(), new HashSet<String>());
 
         Assert.assertNotNull(session.sessions().getUserSession(realm, userSession.getId()));
@@ -463,7 +464,7 @@ public class UserSessionProviderTest {
 
     @Test
     public void testClientLoginSessions() {
-        UserSessionModel userSession = session.sessions().createUserSession(realm, session.users().getUserByUsername("user1", realm), "user1", "127.0.0.2", "form", true, null, null);
+        UserSessionModel userSession = session.sessions().createUserSession(KeycloakModelUtils.generateId(), realm, session.users().getUserByUsername("user1", realm), "user1", "127.0.0.2", "form", true, null, null);
 
         ClientModel client1 = realm.getClientByClientId("test-app");
         ClientModel client2 = realm.getClientByClientId("third-party");
@@ -527,6 +528,27 @@ public class UserSessionProviderTest {
         Assert.assertNull(clientSessions.get(client1.getId()));
     }
 
+
+    @Test
+    public void testFailCreateExistingSession() {
+        UserSessionModel userSession = session.sessions().createUserSession("123", realm, session.users().getUserByUsername("user1", realm), "user1", "127.0.0.2", "form", true, null, null);
+
+        // commit
+        resetSession();
+
+
+        try {
+            session.sessions().createUserSession("123", realm, session.users().getUserByUsername("user1", realm), "user1", "127.0.0.2", "form", true, null, null);
+            kc.stopSession(session, true);
+            Assert.fail("Not expected to successfully create duplicated userSession");
+        } catch (IllegalStateException e) {
+            // Expected
+            session = kc.startSession();
+        }
+
+    }
+
+
     private void testClientLoginSession(AuthenticatedClientSessionModel clientLoginSession, String expectedClientId, String expectedUserSessionId, String expectedAction, int expectedTimestamp) {
         Assert.assertEquals(expectedClientId, clientLoginSession.getClient().getClientId());
         Assert.assertEquals(expectedUserSessionId, clientLoginSession.getUserSession().getId());
@@ -632,7 +654,7 @@ public class UserSessionProviderTest {
 
     private UserSessionModel[] createSessions() {
         UserSessionModel[] sessions = new UserSessionModel[3];
-        sessions[0] = session.sessions().createUserSession(realm, session.users().getUserByUsername("user1", realm), "user1", "127.0.0.1", "form", true, null, null);
+        sessions[0] = session.sessions().createUserSession(KeycloakModelUtils.generateId(), realm, session.users().getUserByUsername("user1", realm), "user1", "127.0.0.1", "form", true, null, null);
 
         Set<String> roles = new HashSet<String>();
         roles.add("one");
@@ -645,10 +667,10 @@ public class UserSessionProviderTest {
         createClientSession(realm.getClientByClientId("test-app"), sessions[0], "http://redirect", "state", roles, protocolMappers);
         createClientSession(realm.getClientByClientId("third-party"), sessions[0], "http://redirect", "state", new HashSet<String>(), new HashSet<String>());
 
-        sessions[1] = session.sessions().createUserSession(realm, session.users().getUserByUsername("user1", realm), "user1", "127.0.0.2", "form", true, null, null);
+        sessions[1] = session.sessions().createUserSession(KeycloakModelUtils.generateId(), realm, session.users().getUserByUsername("user1", realm), "user1", "127.0.0.2", "form", true, null, null);
         createClientSession(realm.getClientByClientId("test-app"), sessions[1], "http://redirect", "state", new HashSet<String>(), new HashSet<String>());
 
-        sessions[2] = session.sessions().createUserSession(realm, session.users().getUserByUsername("user2", realm), "user2", "127.0.0.3", "form", true, null, null);
+        sessions[2] = session.sessions().createUserSession(KeycloakModelUtils.generateId(), realm, session.users().getUserByUsername("user2", realm), "user2", "127.0.0.3", "form", true, null, null);
         createClientSession(realm.getClientByClientId("test-app"), sessions[2], "http://redirect", "state", new HashSet<String>(), new HashSet<String>());
 
         resetSession();
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/KeycloakRule.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/KeycloakRule.java
index 9c61e05..2904ef8 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/KeycloakRule.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/KeycloakRule.java
@@ -21,7 +21,6 @@ import org.keycloak.Config;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.UserSessionModel;
-import org.keycloak.services.managers.ClientSessionCode;
 import org.keycloak.services.managers.RealmManager;
 import org.keycloak.testsuite.ApplicationServlet;
 
diff --git a/testsuite/integration/src/test/resources/log4j.properties b/testsuite/integration/src/test/resources/log4j.properties
index decc0aa..4ba41af 100755
--- a/testsuite/integration/src/test/resources/log4j.properties
+++ b/testsuite/integration/src/test/resources/log4j.properties
@@ -80,7 +80,6 @@ log4j.logger.org.apache.directory.server.ldap.LdapProtocolHandler=error
 #log4j.logger.org.apache.http.impl.conn=debug
 
 # Enable to view details from identity provider authenticator
-# log4j.logger.org.keycloak.authentication.authenticators.browser.IdentityProviderAuthenticator=trace
-
-# TODO: Remove
-log4j.logger.org.keycloak.models.sessions.infinispan.InfinispanUserSessionProvider=debug
\ No newline at end of file
+log4j.logger.org.keycloak.authentication.authenticators.browser.IdentityProviderAuthenticator=trace
+log4j.logger.org.keycloak.services.resources.IdentityBrokerService=trace
+log4j.logger.org.keycloak.broker=trace
diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/forms/PassThroughRegistration.java b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/forms/PassThroughRegistration.java
index 74906bd..d03c0b0 100755
--- a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/forms/PassThroughRegistration.java
+++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/forms/PassThroughRegistration.java
@@ -52,7 +52,7 @@ public class PassThroughRegistration implements Authenticator, AuthenticatorFact
         user.setEnabled(true);
 
         user.setEmail(email);
-        context.getAuthenticationSession().setNote(OIDCLoginProtocol.LOGIN_HINT_PARAM, username);
+        context.getAuthenticationSession().setClientNote(OIDCLoginProtocol.LOGIN_HINT_PARAM, username);
         context.setUser(user);
         context.getEvent().user(user);
         context.getEvent().success();
diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/TestingResourceProvider.java b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/TestingResourceProvider.java
index a4ae8c2..b868827 100644
--- a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/TestingResourceProvider.java
+++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/TestingResourceProvider.java
@@ -163,6 +163,7 @@ public class TestingResourceProvider implements RealmResourceProvider {
         }
 
         session.sessions().removeExpired(realm);
+        session.authenticationSessions().removeExpired(realm);
         return Response.ok().build();
     }
 
diff --git a/testsuite/integration-arquillian/test-apps/servlets/src/main/java/org/keycloak/testsuite/adapter/servlet/ClientInitiatedAccountLinkServlet.java b/testsuite/integration-arquillian/test-apps/servlets/src/main/java/org/keycloak/testsuite/adapter/servlet/ClientInitiatedAccountLinkServlet.java
index 45bbc60..f8849b0 100644
--- a/testsuite/integration-arquillian/test-apps/servlets/src/main/java/org/keycloak/testsuite/adapter/servlet/ClientInitiatedAccountLinkServlet.java
+++ b/testsuite/integration-arquillian/test-apps/servlets/src/main/java/org/keycloak/testsuite/adapter/servlet/ClientInitiatedAccountLinkServlet.java
@@ -47,7 +47,7 @@ public class ClientInitiatedAccountLinkServlet extends HttpServlet {
             String realm = request.getParameter("realm");
             KeycloakSecurityContext session = (KeycloakSecurityContext) request.getAttribute(KeycloakSecurityContext.class.getName());
             AccessToken token = session.getToken();
-            String clientSessionId = token.getClientSession();
+            String clientId = token.getAudience()[0];
             String nonce = UUID.randomUUID().toString();
             MessageDigest md = null;
             try {
@@ -55,7 +55,7 @@ public class ClientInitiatedAccountLinkServlet extends HttpServlet {
             } catch (NoSuchAlgorithmException e) {
                 throw new RuntimeException(e);
             }
-            String input = nonce + token.getSessionState() + clientSessionId + provider;
+            String input = nonce + token.getSessionState() + clientId + provider;
             byte[] check = md.digest(input.getBytes(StandardCharsets.UTF_8));
             String hash = Base64Url.encode(check);
             request.getSession().setAttribute("hash", hash);
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/InfoPage.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/InfoPage.java
index df8f1d0..346a0db 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/InfoPage.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/InfoPage.java
@@ -33,6 +33,9 @@ public class InfoPage extends AbstractPage {
     @FindBy(className = "instruction")
     private WebElement infoMessage;
 
+    @FindBy(linkText = "« Back to Application")
+    private WebElement backToApplicationLink;
+
     public String getInfo() {
         return infoMessage.getText();
     }
@@ -46,4 +49,8 @@ public class InfoPage extends AbstractPage {
         throw new UnsupportedOperationException();
     }
 
+    public void clickBackToApplicationLink() {
+        backToApplicationLink.click();
+    }
+
 }
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/LoginExpiredPage.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/LoginExpiredPage.java
new file mode 100644
index 0000000..e3ff938
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/LoginExpiredPage.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.testsuite.pages;
+
+import org.openqa.selenium.WebElement;
+import org.openqa.selenium.support.FindBy;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class LoginExpiredPage extends AbstractPage {
+
+    @FindBy(id = "loginRestartLink")
+    private WebElement loginRestartLink;
+
+    @FindBy(id = "loginContinueLink")
+    private WebElement loginContinueLink;
+
+
+    public void clickLoginRestartLink() {
+        loginRestartLink.click();
+    }
+
+    public void clickLoginContinueLink() {
+        loginContinueLink.click();
+    }
+
+
+    public boolean isCurrent() {
+        return driver.getTitle().equals("Page has expired");
+    }
+
+    public void open() {
+        throw new UnsupportedOperationException();
+    }
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/RegisterPage.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/RegisterPage.java
index 810ba84..0fb07bf 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/RegisterPage.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/RegisterPage.java
@@ -54,6 +54,9 @@ public class RegisterPage extends AbstractPage {
     @FindBy(className = "instruction")
     private WebElement loginInstructionMessage;
 
+    @FindBy(linkText = "« Back to Login")
+    private WebElement backToLoginLink;
+
 
     public void register(String firstName, String lastName, String email, String username, String password, String passwordConfirm) {
         firstNameInput.clear();
@@ -125,6 +128,10 @@ public class RegisterPage extends AbstractPage {
         submitButton.click();
     }
 
+    public void clickBackToLogin() {
+        backToLoginLink.click();
+    }
+
     public String getError() {
         return loginErrorMessage != null ? loginErrorMessage.getText() : null;
     }
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/OAuthClient.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/OAuthClient.java
index 682e745..7489ee2 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/OAuthClient.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/OAuthClient.java
@@ -41,6 +41,7 @@ import org.keycloak.constants.AdapterConstants;
 import org.keycloak.jose.jwk.JSONWebKeySet;
 import org.keycloak.jose.jws.JWSInput;
 import org.keycloak.jose.jws.crypto.RSAProvider;
+import org.keycloak.models.utils.KeycloakModelUtils;
 import org.keycloak.protocol.oidc.OIDCLoginProtocol;
 import org.keycloak.protocol.oidc.OIDCLoginProtocolService;
 import org.keycloak.protocol.oidc.utils.OIDCResponseType;
@@ -73,7 +74,7 @@ import java.util.*;
  */
 public class OAuthClient {
     public static final String SERVER_ROOT = AuthServerTestEnricher.getAuthServerContextRoot();
-    public static final String AUTH_SERVER_ROOT = SERVER_ROOT + "/auth";
+    public static String AUTH_SERVER_ROOT = SERVER_ROOT + "/auth";
     public static final String APP_ROOT = AUTH_SERVER_ROOT + "/realms/master/app";
     private static final boolean sslRequired = Boolean.parseBoolean(System.getProperty("auth.server.ssl.required"));
 
@@ -89,7 +90,7 @@ public class OAuthClient {
 
     private String redirectUri;
 
-    private String state;
+    private StateParamProvider state;
 
     private String scope;
 
@@ -162,7 +163,7 @@ public class OAuthClient {
         realm = "test";
         clientId = "test-app";
         redirectUri = APP_ROOT + "/auth";
-        state = "mystate";
+        state = KeycloakModelUtils::generateId;
         scope = null;
         uiLocales = null;
         clientSessionState = null;
@@ -607,6 +608,7 @@ public class OAuthClient {
         if (redirectUri != null) {
             b.queryParam(OAuth2Constants.REDIRECT_URI, redirectUri);
         }
+        String state = this.state.getState();
         if (state != null) {
             b.queryParam(OAuth2Constants.STATE, state);
         }
@@ -692,8 +694,17 @@ public class OAuthClient {
         return this;
     }
 
-    public OAuthClient state(String state) {
-        this.state = state;
+    public OAuthClient stateParamHardcoded(String value) {
+        this.state = () -> {
+            return value;
+        };
+        return this;
+    }
+
+    public OAuthClient stateParamRandom() {
+        this.state = () -> {
+            return KeycloakModelUtils.generateId();
+        };
         return this;
     }
 
@@ -927,4 +938,12 @@ public class OAuthClient {
         return publicKeys.get(realm);
     }
 
+
+    private interface StateParamProvider {
+
+        String getState();
+
+    }
+
+
 }
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/AccountTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/AccountTest.java
index dcc4246..eba81f4 100755
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/AccountTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/AccountTest.java
@@ -158,7 +158,6 @@ public class AccountTest extends AbstractTestRealmKeycloakTest {
 
     @Before
     public void before() {
-        oauth.state("mystate"); // keycloak enforces that a state param has been sent by client
         userId = findUser("test-user@localhost").getId();
 
         // Revert any password policy and user password changes
@@ -854,7 +853,6 @@ public class AccountTest extends AbstractTestRealmKeycloakTest {
         try {
             OAuthClient oauth2 = new OAuthClient();
             oauth2.init(adminClient, driver2);
-            oauth2.state("mystate");
             oauth2.doLogin("view-sessions", "password");
 
             EventRepresentation login2Event = events.expectLogin().user(userId).detail(Details.USERNAME, "view-sessions").assertEvent();
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/actions/RequiredActionEmailVerificationTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/actions/RequiredActionEmailVerificationTest.java
index eb719d8..1846d42 100755
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/actions/RequiredActionEmailVerificationTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/actions/RequiredActionEmailVerificationTest.java
@@ -87,9 +87,6 @@ public class RequiredActionEmailVerificationTest extends AbstractTestRealmKeyclo
 
     @Before
     public void before() {
-        oauth.state("mystate"); // have to set this as keycloak validates that state is sent
-
-
         ApiUtil.removeUserByUsername(testRealm(), "test-user@localhost");
         UserRepresentation user = UserBuilder.create().enabled(true)
                 .username("test-user@localhost")
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/actions/RequiredActionResetPasswordTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/actions/RequiredActionResetPasswordTest.java
index d457b0b..1f68b59 100755
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/actions/RequiredActionResetPasswordTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/actions/RequiredActionResetPasswordTest.java
@@ -59,11 +59,6 @@ public class RequiredActionResetPasswordTest extends AbstractTestRealmKeycloakTe
     @Page
     protected LoginPasswordUpdatePage changePasswordPage;
 
-    @Before
-    public void before() {
-        oauth.state("mystate"); // have to set this as keycloak validates that state is sent
-    }
-
 
     @Test
     public void tempPassword() throws Exception {
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractClientInitiatedAccountLinkTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractClientInitiatedAccountLinkTest.java
index 72e8c46..750df03 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractClientInitiatedAccountLinkTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractClientInitiatedAccountLinkTest.java
@@ -38,10 +38,14 @@ import org.keycloak.representations.idm.RealmRepresentation;
 import org.keycloak.representations.idm.RoleRepresentation;
 import org.keycloak.representations.idm.UserRepresentation;
 import org.keycloak.testsuite.adapter.AbstractServletsAdapterTest;
+import org.keycloak.testsuite.admin.ApiUtil;
 import org.keycloak.testsuite.arquillian.AuthServerTestEnricher;
 import org.keycloak.testsuite.broker.BrokerTestTools;
 import org.keycloak.testsuite.page.AbstractPageWithInjectedUrl;
+import org.keycloak.testsuite.pages.AccountUpdateProfilePage;
+import org.keycloak.testsuite.pages.ErrorPage;
 import org.keycloak.testsuite.pages.LoginPage;
+import org.keycloak.testsuite.pages.LoginUpdateProfilePage;
 import org.keycloak.testsuite.pages.UpdateAccountInformationPage;
 import org.keycloak.testsuite.util.OAuthClient;
 import org.keycloak.testsuite.util.WaitUtils;
@@ -72,11 +76,17 @@ public abstract class AbstractClientInitiatedAccountLinkTest extends AbstractSer
     public static final String PARENT_USERNAME = "parent";
 
     @Page
-    protected UpdateAccountInformationPage profilePage;
+    protected LoginUpdateProfilePage loginUpdateProfilePage;
+
+    @Page
+    protected AccountUpdateProfilePage profilePage;
 
     @Page
     private LoginPage loginPage;
 
+    @Page
+    protected ErrorPage errorPage;
+
     public static class ClientApp extends AbstractPageWithInjectedUrl {
 
         public static final String DEPLOYMENT_NAME = "client-linking";
@@ -532,6 +542,92 @@ public abstract class AbstractClientInitiatedAccountLinkTest extends AbstractSer
 
     }
 
+
+    @Test
+    public void testAccountNotLinkedAutomatically() throws Exception {
+        RealmResource realm = adminClient.realms().realm(CHILD_IDP);
+        List<FederatedIdentityRepresentation> links = realm.users().get(childUserId).getFederatedIdentity();
+        Assert.assertTrue(links.isEmpty());
+
+        // Login to account mgmt first
+        profilePage.open(CHILD_IDP);
+        WaitUtils.waitForPageToLoad(driver);
+
+        Assert.assertTrue(loginPage.isCurrent(CHILD_IDP));
+        loginPage.login("child", "password");
+        profilePage.assertCurrent();
+
+        // Now in another tab, open login screen with "prompt=login" . Login screen will be displayed even if I have SSO cookie
+        UriBuilder linkBuilder = UriBuilder.fromUri(appPage.getInjectedUrl().toString())
+                .path("nosuch");
+        String linkUrl = linkBuilder.clone()
+                .queryParam(OIDCLoginProtocol.PROMPT_PARAM, OIDCLoginProtocol.PROMPT_VALUE_LOGIN)
+                .build().toString();
+
+        navigateTo(linkUrl);
+        Assert.assertTrue(loginPage.isCurrent(CHILD_IDP));
+        loginPage.clickSocial(PARENT_IDP);
+        Assert.assertTrue(loginPage.isCurrent(PARENT_IDP));
+        loginPage.login(PARENT_USERNAME, "password");
+
+        // Test I was not automatically linked.
+        links = realm.users().get(childUserId).getFederatedIdentity();
+        Assert.assertTrue(links.isEmpty());
+
+        loginUpdateProfilePage.assertCurrent();
+        loginUpdateProfilePage.update("Joe", "Doe", "joe@parent.com");
+
+        errorPage.assertCurrent();
+        Assert.assertEquals("You are already authenticated as different user 'child' in this session. Please logout first.", errorPage.getError());
+
+        logoutAll();
+
+        // Remove newly created user
+        String newUserId = ApiUtil.findUserByUsername(realm, "parent").getId();
+        getCleanup("child").addUserId(newUserId);
+    }
+
+
+    @Test
+    public void testAccountLinkingExpired() throws Exception {
+        RealmResource realm = adminClient.realms().realm(CHILD_IDP);
+        List<FederatedIdentityRepresentation> links = realm.users().get(childUserId).getFederatedIdentity();
+        Assert.assertTrue(links.isEmpty());
+
+        // Login to account mgmt first
+        profilePage.open(CHILD_IDP);
+        WaitUtils.waitForPageToLoad(driver);
+
+        Assert.assertTrue(loginPage.isCurrent(CHILD_IDP));
+        loginPage.login("child", "password");
+        profilePage.assertCurrent();
+
+        // Now in another tab, request account linking
+        UriBuilder linkBuilder = UriBuilder.fromUri(appPage.getInjectedUrl().toString())
+                .path("link");
+        String linkUrl = linkBuilder.clone()
+                .queryParam("realm", CHILD_IDP)
+                .queryParam("provider", PARENT_IDP).build().toString();
+        navigateTo(linkUrl);
+
+        Assert.assertTrue(loginPage.isCurrent(PARENT_IDP));
+
+        // Logout "child" userSession in the meantime (for example through admin request)
+        realm.logoutAll();
+
+        // Finish login on parent.
+        loginPage.login(PARENT_USERNAME, "password");
+
+        // Test I was not automatically linked
+        links = realm.users().get(childUserId).getFederatedIdentity();
+        Assert.assertTrue(links.isEmpty());
+
+        errorPage.assertCurrent();
+        Assert.assertEquals("Requested broker account linking, but current session is no longer valid.", errorPage.getError());
+
+        logoutAll();
+    }
+
     private void navigateTo(String uri) {
         driver.navigate().to(uri);
         WaitUtils.waitForPageToLoad(driver);
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/BrowserButtonsTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/BrowserButtonsTest.java
new file mode 100644
index 0000000..6cbe92f
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/BrowserButtonsTest.java
@@ -0,0 +1,376 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.testsuite.forms;
+
+import java.io.IOException;
+
+import javax.mail.MessagingException;
+import javax.mail.internet.MimeMessage;
+
+import org.jboss.arquillian.graphene.page.Page;
+import org.junit.Before;
+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.UserModel;
+import org.keycloak.representations.idm.RealmRepresentation;
+import org.keycloak.representations.idm.UserRepresentation;
+import org.keycloak.testsuite.AbstractTestRealmKeycloakTest;
+import org.keycloak.testsuite.AssertEvents;
+import org.keycloak.testsuite.admin.ApiUtil;
+import org.keycloak.testsuite.pages.AppPage;
+import org.keycloak.testsuite.pages.ErrorPage;
+import org.keycloak.testsuite.pages.InfoPage;
+import org.keycloak.testsuite.pages.LoginExpiredPage;
+import org.keycloak.testsuite.pages.LoginPage;
+import org.keycloak.testsuite.pages.LoginPasswordResetPage;
+import org.keycloak.testsuite.pages.LoginPasswordUpdatePage;
+import org.keycloak.testsuite.pages.LoginUpdateProfilePage;
+import org.keycloak.testsuite.pages.OAuthGrantPage;
+import org.keycloak.testsuite.pages.RegisterPage;
+import org.keycloak.testsuite.pages.VerifyEmailPage;
+import org.keycloak.testsuite.util.GreenMailRule;
+import org.keycloak.testsuite.util.OAuthClient;
+import org.keycloak.testsuite.util.UserBuilder;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Test for browser back/forward/refresh buttons
+ *
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class BrowserButtonsTest extends AbstractTestRealmKeycloakTest {
+
+    private String userId;
+
+    @Override
+    public void configureTestRealm(RealmRepresentation testRealm) {
+    }
+
+    @Before
+    public void setup() {
+        UserRepresentation user = UserBuilder.create()
+                .username("login-test")
+                .email("login@test.com")
+                .enabled(true)
+                .requiredAction(UserModel.RequiredAction.UPDATE_PROFILE.toString())
+                .requiredAction(UserModel.RequiredAction.UPDATE_PASSWORD.toString())
+                .build();
+
+        userId = ApiUtil.createUserAndResetPasswordWithAdminClient(testRealm(), user, "password");
+        expectedMessagesCount = 0;
+        getCleanup().addUserId(userId);
+
+        oauth.clientId("test-app");
+    }
+
+    @Rule
+    public GreenMailRule greenMail = new GreenMailRule();
+
+    @Page
+    protected AppPage appPage;
+
+    @Page
+    protected LoginPage loginPage;
+
+    @Page
+    protected ErrorPage errorPage;
+
+    @Page
+    protected InfoPage infoPage;
+
+    @Page
+    protected VerifyEmailPage verifyEmailPage;
+
+    @Page
+    protected LoginPasswordResetPage resetPasswordPage;
+
+    @Page
+    protected LoginPasswordUpdatePage updatePasswordPage;
+
+    @Page
+    protected LoginUpdateProfilePage updateProfilePage;
+
+    @Page
+    protected LoginExpiredPage loginExpiredPage;
+
+    @Page
+    protected RegisterPage registerPage;
+
+    @Page
+    protected OAuthGrantPage grantPage;
+
+    @Rule
+    public AssertEvents events = new AssertEvents(this);
+
+    private int expectedMessagesCount;
+
+
+    // KEYCLOAK-4670 - Flow 1
+    @Test
+    public void invalidLoginAndBackButton() throws IOException, MessagingException {
+        loginPage.open();
+
+        loginPage.login("login-test2", "invalid");
+        loginPage.assertCurrent();
+
+        loginPage.login("login-test3", "invalid");
+        loginPage.assertCurrent();
+
+        // Click browser back. Should be still on login page (TODO: Retest with real browsers like FF or Chrome. Maybe they need some additional actions to confirm re-sending POST request )
+        driver.navigate().back();
+        loginPage.assertCurrent();
+
+        // Click browser refresh. Should be still on login page
+        driver.navigate().refresh();
+        loginPage.assertCurrent();
+    }
+
+
+    // KEYCLOAK-4670 - Flow 2
+    @Test
+    public void requiredActionsBackForwardTest() throws IOException, MessagingException {
+        loginPage.open();
+
+        // Login and assert on "updatePassword" page
+        loginPage.login("login-test", "password");
+        updatePasswordPage.assertCurrent();
+
+        // Update password and assert on "updateProfile" page
+        updatePasswordPage.changePassword("password", "password");
+        updateProfilePage.assertCurrent();
+
+        // Click browser back. Assert on "Page expired" page
+        driver.navigate().back();
+        loginExpiredPage.assertCurrent();
+
+        // Click browser forward. Assert on "updateProfile" page again
+        driver.navigate().forward();
+        updateProfilePage.assertCurrent();
+
+
+        // Successfully update profile and assert user logged
+        updateProfilePage.update("John", "Doe3", "john@doe3.com");
+        appPage.assertCurrent();
+    }
+
+
+    // KEYCLOAK-4670 - Flow 3 extended
+    @Test
+    public void requiredActionsBackAndRefreshTest() throws IOException, MessagingException {
+        loginPage.open();
+
+        // Login and assert on "updatePassword" page
+        loginPage.login("login-test", "password");
+        updatePasswordPage.assertCurrent();
+
+        // Click browser refresh. Assert still on updatePassword page
+        driver.navigate().refresh();
+        updatePasswordPage.assertCurrent();
+
+        // Update password and assert on "updateProfile" page
+        updatePasswordPage.changePassword("password", "password");
+        updateProfilePage.assertCurrent();
+
+        // Click browser back. Assert on "Page expired" page
+        driver.navigate().back();
+        loginExpiredPage.assertCurrent();
+
+        // Click browser refresh. Assert still on "Page expired" page
+        driver.navigate().refresh();
+        loginExpiredPage.assertCurrent();
+
+        // Click "login restart" and assert on loginPage
+        loginExpiredPage.clickLoginRestartLink();
+        loginPage.assertCurrent();
+
+        // Login again and assert on "updateProfile" page
+        loginPage.login("login-test", "password");
+        updateProfilePage.assertCurrent();
+
+        // Click browser back. Assert on "Page expired" page
+        driver.navigate().back();
+        loginExpiredPage.assertCurrent();
+
+        // Click "login continue" and assert on updateProfile page
+        loginExpiredPage.clickLoginContinueLink();
+        updateProfilePage.assertCurrent();
+
+        // Successfully update profile and assert user logged
+        updateProfilePage.update("John", "Doe3", "john@doe3.com");
+        appPage.assertCurrent();
+    }
+
+
+    // KEYCLOAK-4670 - Flow 4
+    @Test
+    public void consentRefresh() {
+        oauth.clientId("third-party");
+
+        // Login and go through required actions
+        loginPage.open();
+        loginPage.login("login-test", "password");
+        updatePasswordPage.changePassword("password", "password");
+        updateProfilePage.update("John", "Doe3", "john@doe3.com");
+
+        // Assert on consent screen
+        grantPage.assertCurrent();
+
+        // Click browser back. Assert on "page expired"
+        driver.navigate().back();
+        loginExpiredPage.assertCurrent();
+
+        // Click continue login. Assert on consent screen again
+        loginExpiredPage.clickLoginContinueLink();
+        grantPage.assertCurrent();
+
+        // Click refresh. Assert still on consent screen
+        driver.navigate().refresh();
+        grantPage.assertCurrent();
+
+        // Confirm consent. Assert authenticated
+        grantPage.accept();
+        appPage.assertCurrent();
+    }
+
+
+    // KEYCLOAK-4670 - Flow 5
+    @Test
+    public void clickBackButtonAfterReturnFromRegister() {
+        loginPage.open();
+        loginPage.clickRegister();
+        registerPage.assertCurrent();
+
+        // Click "Back to login" link on registerPage
+        registerPage.clickBackToLogin();
+        loginPage.assertCurrent();
+
+        // Click browser "back" button. Should be back on register page
+        driver.navigate().back();
+        registerPage.assertCurrent();
+    }
+
+    @Test
+    public void clickBackButtonFromRegisterPage() {
+        loginPage.open();
+        loginPage.clickRegister();
+        registerPage.assertCurrent();
+
+        // Click browser "back" button. Should be back on login page
+        driver.navigate().back();
+        loginPage.assertCurrent();
+    }
+
+
+    @Test
+    public void backButtonToAuthorizationEndpoint() {
+        loginPage.open();
+
+        // Login and assert on "updatePassword" page
+        loginPage.login("login-test", "password");
+        updatePasswordPage.assertCurrent();
+
+        // Click browser back. I should be on 'page expired' . URL corresponds to OIDC AuthorizationEndpoint
+        driver.navigate().back();
+        loginExpiredPage.assertCurrent();
+
+        // Click 'restart' link. I should be on login page
+        loginExpiredPage.clickLoginRestartLink();
+        loginPage.assertCurrent();
+    }
+
+
+    @Test
+    public void backButtonInResetPasswordFlow() throws Exception {
+        // Click on "forgot password" and type username
+        loginPage.open();
+        loginPage.resetPassword();
+
+        resetPasswordPage.assertCurrent();
+
+        resetPasswordPage.changePassword("login-test");
+
+        loginPage.assertCurrent();
+        assertEquals("You should receive an email shortly with further instructions.", loginPage.getSuccessMessage());
+
+        // Receive email
+        MimeMessage message = greenMail.getReceivedMessages()[greenMail.getReceivedMessages().length - 1];
+
+        String changePasswordUrl = ResetPasswordTest.getPasswordResetEmailLink(message);
+
+        driver.navigate().to(changePasswordUrl.trim());
+
+        updatePasswordPage.assertCurrent();
+
+        // Click browser back. Should be on 'page expired'
+        driver.navigate().back();
+        loginExpiredPage.assertCurrent();
+
+        // Click 'continue' should be on updatePasswordPage
+        loginExpiredPage.clickLoginContinueLink();
+        updatePasswordPage.assertCurrent();
+
+        // Click browser back. Should be on 'page expired'
+        driver.navigate().back();
+        loginExpiredPage.assertCurrent();
+
+        // Click 'restart' . Should be on login page
+        loginExpiredPage.clickLoginRestartLink();
+        loginPage.assertCurrent();
+
+    }
+
+
+    @Test
+    public void appInitiatedRegistrationWithBackButton() throws Exception {
+        // Send request from the application directly to 'registrations'
+        String appInitiatedRegisterUrl = oauth.getLoginFormUrl();
+        appInitiatedRegisterUrl = appInitiatedRegisterUrl.replace("openid-connect/auth", "openid-connect/registrations"); // Should be done better way...
+        driver.navigate().to(appInitiatedRegisterUrl);
+        registerPage.assertCurrent();
+
+
+        // Click 'back to login'
+        registerPage.clickBackToLogin();
+        loginPage.assertCurrent();
+
+        // Login
+        loginPage.login("login-test", "password");
+        updatePasswordPage.assertCurrent();
+
+        // Click browser back. Should be on 'page expired'
+        driver.navigate().back();
+        loginExpiredPage.assertCurrent();
+
+        // Click 'continue' should be on updatePasswordPage
+        loginExpiredPage.clickLoginContinueLink();
+        updatePasswordPage.assertCurrent();
+
+        // Click browser back. Should be on 'page expired'
+        driver.navigate().back();
+        loginExpiredPage.assertCurrent();
+
+        // Click 'restart' . Check that I was put to the registration page as flow was initiated as registration flow
+        loginExpiredPage.clickLoginRestartLink();
+        registerPage.assertCurrent();
+    }
+
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/LoginTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/LoginTest.java
index 63ed003..0700677 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/LoginTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/LoginTest.java
@@ -23,21 +23,26 @@ import org.junit.Test;
 import org.keycloak.OAuth2Constants;
 import org.keycloak.admin.client.resource.UserResource;
 import org.keycloak.events.Details;
+import org.keycloak.events.Errors;
 import org.keycloak.events.EventType;
 import org.keycloak.models.BrowserSecurityHeaders;
+import org.keycloak.models.Constants;
 import org.keycloak.representations.idm.EventRepresentation;
 import org.keycloak.representations.idm.RealmRepresentation;
 import org.keycloak.representations.idm.UserRepresentation;
 import org.keycloak.testsuite.AssertEvents;
 import org.keycloak.testsuite.AbstractTestRealmKeycloakTest;
 import org.keycloak.testsuite.admin.ApiUtil;
+import org.keycloak.testsuite.arquillian.AuthServerTestEnricher;
 import org.keycloak.testsuite.pages.AppPage;
 import org.keycloak.testsuite.pages.AppPage.RequestType;
 import org.keycloak.testsuite.pages.ErrorPage;
 import org.keycloak.testsuite.pages.LoginPage;
 import org.keycloak.testsuite.pages.LoginPasswordUpdatePage;
+import org.keycloak.testsuite.util.OAuthClient;
 import org.keycloak.testsuite.util.RealmBuilder;
 import org.keycloak.testsuite.util.UserBuilder;
+import org.openqa.selenium.NoSuchElementException;
 
 import javax.ws.rs.client.Client;
 import javax.ws.rs.client.ClientBuilder;
@@ -47,6 +52,7 @@ import java.util.Map;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 
 /**
@@ -395,18 +401,7 @@ public class LoginTest extends AbstractTestRealmKeycloakTest {
         events.expectLogin().user(userId).detail(Details.USERNAME, "login-test").assertEvent().getSessionId();
     }
 
-    @Test
-    public void loginTimeout() {
-        loginPage.open();
-
-        setTimeOffset(1850);
-
-        loginPage.login("login-test", "password");
-
-        setTimeOffset(0);
 
-        events.expectLogin().clearDetails().detail(Details.CODE_ID, AssertEvents.isCodeId()).user((String) null).session((String) null).error("expired_code").assertEvent().getSessionId();
-    }
 
     @Test
     public void loginLoginHint() {
@@ -555,11 +550,33 @@ public class LoginTest extends AbstractTestRealmKeycloakTest {
         }
     }
 
+
+    // Login timeout scenarios
+
     // KEYCLOAK-1037
     @Test
     public void loginExpiredCode() {
         loginPage.open();
         setTimeOffset(5000);
+        // No explicitly call "removeExpired". Hence authSession will still exists, but will be expired
+        //testingClient.testing().removeExpired("test");
+
+        loginPage.login("login@test.com", "password");
+        loginPage.assertCurrent();
+
+        Assert.assertEquals("You took too long to login. Login process starting from beginning.", loginPage.getError());
+        setTimeOffset(0);
+
+        events.expectLogin().user((String) null).session((String) null).error(Errors.EXPIRED_CODE).clearDetails()
+                .assertEvent();
+    }
+
+    // KEYCLOAK-1037
+    @Test
+    public void loginExpiredCodeWithExplicitRemoveExpired() {
+        loginPage.open();
+        setTimeOffset(5000);
+        // Explicitly call "removeExpired". Hence authSession won't exist, but will be restarted from the KC_RESTART
         testingClient.testing().removeExpired("test");
 
         loginPage.login("login@test.com", "password");
@@ -567,13 +584,68 @@ public class LoginTest extends AbstractTestRealmKeycloakTest {
         //loginPage.assertCurrent();
         loginPage.assertCurrent();
 
-        //Assert.assertEquals("Login timeout. Please login again.", loginPage.getError());
+        Assert.assertEquals("You took too long to login. Login process starting from beginning.", loginPage.getError());
         setTimeOffset(0);
 
-        events.expectLogin().user((String) null).session((String) null).error("expired_code").clearDetails()
+        events.expectLogin().user((String) null).session((String) null).error(Errors.EXPIRED_CODE).clearDetails()
                 .detail(Details.RESTART_AFTER_TIMEOUT, "true")
                 .client((String) null)
                 .assertEvent();
     }
 
+
+    @Test
+    public void loginExpiredCodeAndExpiredCookies() {
+        loginPage.open();
+
+        driver.manage().deleteAllCookies();
+
+        // Cookies are expired including KC_RESTART. No way to continue login. Error page must be shown
+        loginPage.login("login@test.com", "password");
+        errorPage.assertCurrent();
+    }
+
+
+
+    @Test
+    public void openLoginFormWithDifferentApplication() throws Exception {
+        // Login form shown after redirect from admin console
+        oauth.clientId(Constants.ADMIN_CONSOLE_CLIENT_ID);
+        oauth.redirectUri(AuthServerTestEnricher.getAuthServerContextRoot() + "/auth/admin/test/console");
+        oauth.openLoginForm();
+
+        // Login form shown after redirect from app
+        oauth.clientId("test-app");
+        oauth.redirectUri(OAuthClient.APP_ROOT + "/auth");
+        oauth.openLoginForm();
+
+        assertTrue(loginPage.isCurrent());
+        loginPage.login("test-user@localhost", "password");
+        appPage.assertCurrent();
+
+        events.expectLogin().detail(Details.USERNAME, "test-user@localhost").assertEvent();
+    }
+
+    @Test
+    public void openLoginFormAfterExpiredCode() throws Exception {
+        oauth.openLoginForm();
+
+        setTimeOffset(5000);
+
+        oauth.openLoginForm();
+
+        loginPage.assertCurrent();
+        try {
+            String loginError = loginPage.getError();
+            Assert.fail("Not expected to have error on loginForm. Error is: " + loginError);
+        } catch (NoSuchElementException nsee) {
+            // Expected
+        }
+
+        loginPage.login("test-user@localhost", "password");
+        appPage.assertCurrent();
+
+        events.expectLogin().detail(Details.USERNAME, "test-user@localhost").assertEvent();
+    }
+
 }
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/LogoutTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/LogoutTest.java
index db66deb..7fc3f74 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/LogoutTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/LogoutTest.java
@@ -126,7 +126,7 @@ public class LogoutTest extends AbstractTestRealmKeycloakTest {
 
          // Check session 1 not logged-in
         oauth.openLoginForm();
-        assertEquals(oauth.getLoginFormUrl(), driver.getCurrentUrl());
+        loginPage.assertCurrent();
 
         // Login session 3
         oauth.doLogin("test-user@localhost", "password");
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/MultipleTabsLoginTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/MultipleTabsLoginTest.java
new file mode 100644
index 0000000..cec079d
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/MultipleTabsLoginTest.java
@@ -0,0 +1,248 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.testsuite.forms;
+
+import org.jboss.arquillian.graphene.page.Page;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.keycloak.models.UserModel;
+import org.keycloak.representations.idm.RealmRepresentation;
+import org.keycloak.representations.idm.UserRepresentation;
+import org.keycloak.testsuite.AbstractTestRealmKeycloakTest;
+import org.keycloak.testsuite.Assert;
+import org.keycloak.testsuite.AssertEvents;
+import org.keycloak.testsuite.admin.ApiUtil;
+import org.keycloak.testsuite.pages.AppPage;
+import org.keycloak.testsuite.pages.ErrorPage;
+import org.keycloak.testsuite.pages.InfoPage;
+import org.keycloak.testsuite.pages.LoginExpiredPage;
+import org.keycloak.testsuite.pages.LoginPage;
+import org.keycloak.testsuite.pages.LoginPasswordResetPage;
+import org.keycloak.testsuite.pages.LoginPasswordUpdatePage;
+import org.keycloak.testsuite.pages.LoginUpdateProfilePage;
+import org.keycloak.testsuite.pages.OAuthGrantPage;
+import org.keycloak.testsuite.pages.RegisterPage;
+import org.keycloak.testsuite.pages.VerifyEmailPage;
+import org.keycloak.testsuite.util.GreenMailRule;
+import org.keycloak.testsuite.util.UserBuilder;
+
+/**
+ * Tries to test multiple browser tabs
+ *
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class MultipleTabsLoginTest extends AbstractTestRealmKeycloakTest {
+
+    private String userId;
+
+    @Override
+    public void configureTestRealm(RealmRepresentation testRealm) {
+    }
+
+    @Before
+    public void setup() {
+        UserRepresentation user = UserBuilder.create()
+                .username("login-test")
+                .email("login@test.com")
+                .enabled(true)
+                .requiredAction(UserModel.RequiredAction.UPDATE_PROFILE.toString())
+                .requiredAction(UserModel.RequiredAction.UPDATE_PASSWORD.toString())
+                .build();
+
+        userId = ApiUtil.createUserAndResetPasswordWithAdminClient(testRealm(), user, "password");
+        getCleanup().addUserId(userId);
+
+        oauth.clientId("test-app");
+    }
+
+    @Rule
+    public GreenMailRule greenMail = new GreenMailRule();
+
+    @Page
+    protected AppPage appPage;
+
+    @Page
+    protected LoginPage loginPage;
+
+    @Page
+    protected ErrorPage errorPage;
+
+    @Page
+    protected InfoPage infoPage;
+
+    @Page
+    protected VerifyEmailPage verifyEmailPage;
+
+    @Page
+    protected LoginPasswordResetPage resetPasswordPage;
+
+    @Page
+    protected LoginPasswordUpdatePage updatePasswordPage;
+
+    @Page
+    protected LoginUpdateProfilePage updateProfilePage;
+
+    @Page
+    protected LoginExpiredPage loginExpiredPage;
+
+    @Page
+    protected RegisterPage registerPage;
+
+    @Page
+    protected OAuthGrantPage grantPage;
+
+    @Rule
+    public AssertEvents events = new AssertEvents(this);
+
+
+    // Test for scenario when user is logged into JS application in 2 browser tabs. Then click "logout" in tab1 and he is logged-out from both tabs (tab2 is logged-out automatically due to session iframe few seconds later)
+    // Now both browser tabs show the 1st login screen and we need to make sure that actionURL (code with execution) is valid on both tabs, so user won't have error page when he tries to login from tab1
+    @Test
+    public void openMultipleTabs() {
+        oauth.openLoginForm();
+        loginPage.assertCurrent();
+        String actionUrl1 = getActionUrl(driver.getPageSource());
+
+        oauth.openLoginForm();
+        loginPage.assertCurrent();
+        String actionUrl2 = getActionUrl(driver.getPageSource());
+
+        Assert.assertEquals(actionUrl1, actionUrl2);
+
+    }
+
+
+    private String getActionUrl(String pageSource) {
+        return pageSource.split("action=\"")[1].split("\"")[0].replaceAll("&amp;", "&");
+    }
+
+
+    @Test
+    public void multipleTabsParallelLoginTest() {
+        oauth.openLoginForm();
+        loginPage.assertCurrent();
+
+        loginPage.login("login-test", "password");
+        updatePasswordPage.assertCurrent();
+
+        String tab1Url = driver.getCurrentUrl();
+
+        // Simulate login in different browser tab tab2. I will be on loginPage again.
+        oauth.openLoginForm();
+        loginPage.assertCurrent();
+
+        // Login in tab2
+        loginPage.login("login-test", "password");
+        updatePasswordPage.assertCurrent();
+
+        updatePasswordPage.changePassword("password", "password");
+        updateProfilePage.update("John", "Doe3", "john@doe3.com");
+        appPage.assertCurrent();
+
+        // Try to go back to tab 1. We should have ALREADY_LOGGED_IN info page
+        driver.navigate().to(tab1Url);
+        infoPage.assertCurrent();
+        Assert.assertEquals("You are already logged in.", infoPage.getInfo());
+
+        infoPage.clickBackToApplicationLink();
+        appPage.assertCurrent();
+    }
+
+
+    @Test
+    public void expiredAuthenticationAction_currentCodeExpiredExecution() {
+        // Simulate to open login form in 2 tabs
+        oauth.openLoginForm();
+        loginPage.assertCurrent();
+        String actionUrl1 = getActionUrl(driver.getPageSource());
+
+        // Click "register" in tab2
+        loginPage.clickRegister();
+        registerPage.assertCurrent();
+
+        // Simulate going back to tab1 and confirm login form. Page "showExpired" should be shown (NOTE: WebDriver does it with GET, when real browser would do it with POST. Improve test if needed...)
+        driver.navigate().to(actionUrl1);
+        loginExpiredPage.assertCurrent();
+
+        // Click on continue and assert I am on "register" form
+        loginExpiredPage.clickLoginContinueLink();
+        registerPage.assertCurrent();
+
+        // Finally click "Back to login" and authenticate
+        registerPage.clickBackToLogin();
+        loginPage.assertCurrent();
+
+        // Login success now
+        loginPage.login("login-test", "password");
+        updatePasswordPage.changePassword("password", "password");
+        updateProfilePage.update("John", "Doe3", "john@doe3.com");
+        appPage.assertCurrent();
+    }
+
+
+    @Test
+    public void expiredAuthenticationAction_expiredCodeCurrentExecution() {
+        // Simulate to open login form in 2 tabs
+        oauth.openLoginForm();
+        loginPage.assertCurrent();
+        String actionUrl1 = getActionUrl(driver.getPageSource());
+
+        loginPage.login("invalid", "invalid");
+        loginPage.assertCurrent();
+        Assert.assertEquals("Invalid username or password.", loginPage.getError());
+
+        // Simulate going back to tab1 and confirm login form. Login page with "action expired" message should be shown (NOTE: WebDriver does it with GET, when real browser would do it with POST. Improve test if needed...)
+        driver.navigate().to(actionUrl1);
+        loginPage.assertCurrent();
+        Assert.assertEquals("Action expired. Please continue with login now.", loginPage.getError());
+
+        // Login success now
+        loginPage.login("login-test", "password");
+        updatePasswordPage.changePassword("password", "password");
+        updateProfilePage.update("John", "Doe3", "john@doe3.com");
+        appPage.assertCurrent();
+    }
+
+
+    @Test
+    public void expiredAuthenticationAction_expiredCodeExpiredExecution() {
+        // Open tab1
+        oauth.openLoginForm();
+        loginPage.assertCurrent();
+        String actionUrl1 = getActionUrl(driver.getPageSource());
+
+        // Authenticate in tab2
+        loginPage.login("login-test", "password");
+        updatePasswordPage.assertCurrent();
+
+        // Simulate going back to tab1 and confirm login form. Page "Page expired" should be shown (NOTE: WebDriver does it with GET, when real browser would do it with POST. Improve test if needed...)
+        driver.navigate().to(actionUrl1);
+        loginExpiredPage.assertCurrent();
+
+        // Finish login
+        loginExpiredPage.clickLoginContinueLink();
+        updatePasswordPage.assertCurrent();
+
+        updatePasswordPage.changePassword("password", "password");
+        updateProfilePage.update("John", "Doe3", "john@doe3.com");
+        appPage.assertCurrent();
+    }
+
+
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/ResetPasswordTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/ResetPasswordTest.java
index 9afade5..95f5a08 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/ResetPasswordTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/ResetPasswordTest.java
@@ -172,8 +172,7 @@ public class ResetPasswordTest extends AbstractTestRealmKeycloakTest {
         String changePasswordUrl = resetPassword("login-test");
         events.clear();
 
-        // TODO:hmlnarik is this correct??
-        assertSecondPasswordResetFails(changePasswordUrl, "test-app"); // KC_RESTART exists, hence client-ID is taken from it.
+        assertSecondPasswordResetFails(changePasswordUrl, null); // KC_RESTART doesn't exists, it was deleted after first successful reset-password flow was finished
     }
 
     @Test
@@ -195,7 +194,7 @@ public class ResetPasswordTest extends AbstractTestRealmKeycloakTest {
         assertEquals("An error occurred, please login again through your application.", errorPage.getError());
 
         events.expect(EventType.RESET_PASSWORD)
-          .client(clientId)
+          .client((String) null)
           .session((String) null)
           .user(userId)
           .detail(Details.USERNAME, "login-test")
@@ -286,9 +285,10 @@ public class ResetPasswordTest extends AbstractTestRealmKeycloakTest {
     }
 
     private void resetPasswordInvalidPassword(String username, String password, String error) throws IOException, MessagingException {
+
         initiateResetPasswordFromResetPasswordPage(username);
 
-        events.expectRequiredAction(EventType.SEND_RESET_PASSWORD).user(userId).session((String)null)
+        events.expectRequiredAction(EventType.SEND_RESET_PASSWORD).user(userId).session((String) null)
                 .detail(Details.USERNAME, username).detail(Details.EMAIL, "login@test.com").assertEvent();
 
         assertEquals(expectedMessagesCount, greenMail.getReceivedMessages().length);
@@ -299,6 +299,7 @@ public class ResetPasswordTest extends AbstractTestRealmKeycloakTest {
 
         driver.navigate().to(changePasswordUrl.trim());
 
+
         updatePasswordPage.assertCurrent();
 
         updatePasswordPage.changePassword(password, password);
@@ -308,7 +309,7 @@ public class ResetPasswordTest extends AbstractTestRealmKeycloakTest {
         events.expectRequiredAction(EventType.UPDATE_PASSWORD_ERROR).error(Errors.PASSWORD_REJECTED).user(userId).detail(Details.USERNAME, "login-test").assertEvent().getSessionId();
     }
 
-    public void initiateResetPasswordFromResetPasswordPage(String username) {
+    private void initiateResetPasswordFromResetPasswordPage(String username) {
         loginPage.open();
         loginPage.resetPassword();
 
@@ -547,7 +548,7 @@ public class ResetPasswordTest extends AbstractTestRealmKeycloakTest {
     }
 
     @Test
-    public void resetPasswordWithPasswordHisoryPolicy() throws IOException, MessagingException {
+    public void resetPasswordWithPasswordHistoryPolicy() throws IOException, MessagingException {
         //Block passwords that are equal to previous passwords. Default value is 3.
         setPasswordPolicy("passwordHistory");
 
@@ -563,13 +564,14 @@ public class ResetPasswordTest extends AbstractTestRealmKeycloakTest {
             resetPasswordInvalidPassword("login-test", "password1", "Invalid password: must not be equal to any of last 3 passwords.");
             resetPasswordInvalidPassword("login-test", "password2", "Invalid password: must not be equal to any of last 3 passwords.");
 
-            setTimeOffset(8000000);
+            setTimeOffset(6000000);
             resetPassword("login-test", "password3");
 
             resetPasswordInvalidPassword("login-test", "password1", "Invalid password: must not be equal to any of last 3 passwords.");
             resetPasswordInvalidPassword("login-test", "password2", "Invalid password: must not be equal to any of last 3 passwords.");
             resetPasswordInvalidPassword("login-test", "password3", "Invalid password: must not be equal to any of last 3 passwords.");
 
+            setTimeOffset(8000000);
             resetPassword("login-test", "password");
         } finally {
             setTimeOffset(0);
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/SSOTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/SSOTest.java
index 22f75da..90b8049 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/SSOTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/SSOTest.java
@@ -23,9 +23,12 @@ import org.junit.Rule;
 import org.junit.Test;
 import org.keycloak.OAuth2Constants;
 import org.keycloak.events.Details;
+import org.keycloak.events.EventType;
+import org.keycloak.models.UserModel;
 import org.keycloak.representations.IDToken;
 import org.keycloak.representations.idm.EventRepresentation;
 import org.keycloak.representations.idm.RealmRepresentation;
+import org.keycloak.representations.idm.UserRepresentation;
 import org.keycloak.testsuite.AssertEvents;
 import org.keycloak.testsuite.AbstractTestRealmKeycloakTest;
 import org.keycloak.testsuite.drone.Different;
@@ -33,6 +36,7 @@ import org.keycloak.testsuite.pages.AccountUpdateProfilePage;
 import org.keycloak.testsuite.pages.AppPage;
 import org.keycloak.testsuite.pages.AppPage.RequestType;
 import org.keycloak.testsuite.pages.LoginPage;
+import org.keycloak.testsuite.pages.LoginPasswordUpdatePage;
 import org.keycloak.testsuite.util.OAuthClient;
 import org.openqa.selenium.WebDriver;
 
@@ -59,6 +63,9 @@ public class SSOTest extends AbstractTestRealmKeycloakTest {
     @Page
     protected AccountUpdateProfilePage profilePage;
 
+    @Page
+    protected LoginPasswordUpdatePage updatePasswordPage;
+
     @Rule
     public AssertEvents events = new AssertEvents(this);
 
@@ -109,6 +116,7 @@ public class SSOTest extends AbstractTestRealmKeycloakTest {
         events.clear();
     }
 
+
     @Test
     public void multipleSessions() {
         loginPage.open();
@@ -124,7 +132,6 @@ public class SSOTest extends AbstractTestRealmKeycloakTest {
             OAuthClient oauth2 = new OAuthClient();
             oauth2.init(adminClient, driver2);
 
-            oauth2.state("mystate");
             oauth2.doLogin("test-user@localhost", "password");
 
             EventRepresentation login2 = events.expectLogin().assertEvent();
@@ -158,4 +165,38 @@ public class SSOTest extends AbstractTestRealmKeycloakTest {
         }
     }
 
+
+    @Test
+    public void loginWithRequiredActionAddedInTheMeantime() {
+        // SSO login
+        loginPage.open();
+        loginPage.login("test-user@localhost", "password");
+
+        assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
+        Assert.assertNotNull(oauth.getCurrentQuery().get(OAuth2Constants.CODE));
+
+        EventRepresentation loginEvent = events.expectLogin().assertEvent();
+        String sessionId = loginEvent.getSessionId();
+
+        // Add update-profile required action to user now
+        UserRepresentation user = testRealm().users().get(loginEvent.getUserId()).toRepresentation();
+        user.getRequiredActions().add(UserModel.RequiredAction.UPDATE_PASSWORD.toString());
+        testRealm().users().get(loginEvent.getUserId()).update(user);
+
+        // Attempt SSO login. update-password form is shown
+        oauth.openLoginForm();
+        updatePasswordPage.assertCurrent();
+
+        updatePasswordPage.changePassword("password", "password");
+        events.expectRequiredAction(EventType.UPDATE_PASSWORD).assertEvent();
+
+        assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
+
+        loginEvent = events.expectLogin().removeDetail(Details.USERNAME).client("test-app").assertEvent();
+        String sessionId2 = loginEvent.getSessionId();
+        assertEquals(sessionId, sessionId2);
+
+
+    }
+
 }
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/AuthorizationCodeTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/AuthorizationCodeTest.java
index 729f0c5..0ca7785 100755
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/AuthorizationCodeTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/AuthorizationCodeTest.java
@@ -16,8 +16,6 @@
  */
 package org.keycloak.testsuite.oauth;
 
-import org.jboss.arquillian.graphene.page.Page;
-import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Rule;
@@ -31,7 +29,6 @@ import org.keycloak.protocol.oidc.utils.OIDCResponseMode;
 import org.keycloak.representations.idm.RealmRepresentation;
 import org.keycloak.testsuite.AbstractKeycloakTest;
 import org.keycloak.testsuite.AssertEvents;
-import org.keycloak.testsuite.pages.ErrorPage;
 import org.keycloak.testsuite.util.ClientManager;
 import org.keycloak.testsuite.util.OAuthClient;
 import org.openqa.selenium.By;
@@ -61,11 +58,12 @@ public class AuthorizationCodeTest extends AbstractKeycloakTest {
     public void clientConfiguration() {
         oauth.responseType(OAuth2Constants.CODE);
         oauth.responseMode(null);
+        oauth.stateParamRandom();
     }
 
     @Test
     public void authorizationRequest() throws IOException {
-        oauth.state("OpenIdConnect.AuthenticationProperties=2302984sdlk");
+        oauth.stateParamHardcoded("OpenIdConnect.AuthenticationProperties=2302984sdlk");
 
         OAuthClient.AuthorizationEndpointResponse response = oauth.doLogin("test-user@localhost", "password");
 
@@ -100,8 +98,6 @@ public class AuthorizationCodeTest extends AbstractKeycloakTest {
     public void authorizationValidRedirectUri() throws IOException {
         ClientManager.realm(adminClient.realm("test")).clientId("test-app").addRedirectUris(oauth.getRedirectUri());
 
-        oauth.state("mystate");
-
         OAuthClient.AuthorizationEndpointResponse response = oauth.doLogin("test-user@localhost", "password");
 
         Assert.assertTrue(response.isRedirected());
@@ -113,7 +109,7 @@ public class AuthorizationCodeTest extends AbstractKeycloakTest {
 
     @Test
     public void authorizationRequestNoState() throws IOException {
-        oauth.state(null);
+        oauth.stateParamHardcoded(null);
 
         OAuthClient.AuthorizationEndpointResponse response = oauth.doLogin("test-user@localhost", "password");
 
@@ -143,7 +139,7 @@ public class AuthorizationCodeTest extends AbstractKeycloakTest {
     @Test
     public void authorizationRequestFormPostResponseMode() throws IOException {
         oauth.responseMode(OIDCResponseMode.FORM_POST.toString().toLowerCase());
-        oauth.state("OpenIdConnect.AuthenticationProperties=2302984sdlk");
+        oauth.stateParamHardcoded("OpenIdConnect.AuthenticationProperties=2302984sdlk");
         oauth.doLoginGrant("test-user@localhost", "password");
 
         String sources = driver.getPageSource();
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/ResourceOwnerPasswordCredentialsGrantTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/ResourceOwnerPasswordCredentialsGrantTest.java
index de83d4e..efaf4bd 100755
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/ResourceOwnerPasswordCredentialsGrantTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/ResourceOwnerPasswordCredentialsGrantTest.java
@@ -36,6 +36,7 @@ import org.keycloak.representations.idm.ClientRepresentation;
 import org.keycloak.representations.idm.RealmRepresentation;
 import org.keycloak.representations.idm.UserRepresentation;
 import org.keycloak.testsuite.AbstractKeycloakTest;
+import org.keycloak.testsuite.Assert;
 import org.keycloak.testsuite.AssertEvents;
 import org.keycloak.testsuite.util.ClientBuilder;
 import org.keycloak.testsuite.util.ClientManager;
@@ -204,6 +205,8 @@ public class ResourceOwnerPasswordCredentialsGrantTest extends AbstractKeycloakT
                 .removeDetail(Details.CONSENT)
                 .assertEvent();
 
+        Assert.assertTrue(login.equals(accessToken.getPreferredUsername()) || login.equals(accessToken.getEmail()));
+
         assertEquals(accessToken.getSessionState(), refreshToken.getSessionState());
 
         OAuthClient.AccessTokenResponse refreshedResponse = oauth.doRefreshTokenRequest(response.getRefreshToken(), "secret");
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/OIDCAdvancedRequestParamsTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/OIDCAdvancedRequestParamsTest.java
index 57a2102..ac8e359 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/OIDCAdvancedRequestParamsTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/OIDCAdvancedRequestParamsTest.java
@@ -324,6 +324,8 @@ public class OIDCAdvancedRequestParamsTest extends AbstractTestRealmKeycloakTest
 
     @Test
     public void requestParamUnsigned() throws Exception {
+        oauth.stateParamHardcoded("mystate2");
+
         String validRedirectUri = oauth.getRedirectUri();
         TestOIDCEndpointsApplicationResource oidcClientEndpointsResource = testingClient.testApp().oidcClientEndpoints();
 
@@ -344,12 +346,14 @@ public class OIDCAdvancedRequestParamsTest extends AbstractTestRealmKeycloakTest
         oauth.request(requestStr);
         OAuthClient.AuthorizationEndpointResponse response = oauth.doLogin("test-user@localhost", "password");
         Assert.assertNotNull(response.getCode());
-        Assert.assertEquals("mystate", response.getState());
+        Assert.assertEquals("mystate2", response.getState());
         assertTrue(appPage.isCurrent());
     }
 
     @Test
     public void requestUriParamUnsigned() throws Exception {
+        oauth.stateParamHardcoded("mystate1");
+
         String validRedirectUri = oauth.getRedirectUri();
         TestOIDCEndpointsApplicationResource oidcClientEndpointsResource = testingClient.testApp().oidcClientEndpoints();
 
@@ -367,12 +371,14 @@ public class OIDCAdvancedRequestParamsTest extends AbstractTestRealmKeycloakTest
 
         OAuthClient.AuthorizationEndpointResponse response = oauth.doLogin("test-user@localhost", "password");
         Assert.assertNotNull(response.getCode());
-        Assert.assertEquals("mystate", response.getState());
+        Assert.assertEquals("mystate1", response.getState());
         assertTrue(appPage.isCurrent());
     }
 
     @Test
     public void requestUriParamSigned() throws Exception {
+        oauth.stateParamHardcoded("mystate3");
+
         String validRedirectUri = oauth.getRedirectUri();
         TestOIDCEndpointsApplicationResource oidcClientEndpointsResource = testingClient.testApp().oidcClientEndpoints();
 
@@ -412,7 +418,7 @@ public class OIDCAdvancedRequestParamsTest extends AbstractTestRealmKeycloakTest
         // Check signed request_uri will pass
         OAuthClient.AuthorizationEndpointResponse response = oauth.doLogin("test-user@localhost", "password");
         Assert.assertNotNull(response.getCode());
-        Assert.assertEquals("mystate", response.getState());
+        Assert.assertEquals("mystate3", response.getState());
         assertTrue(appPage.isCurrent());
 
         // Revert requiring signature for client
diff --git a/themes/src/main/resources/theme/base/login/login-page-expired.ftl b/themes/src/main/resources/theme/base/login/login-page-expired.ftl
index 5812f31..f58c25d 100644
--- a/themes/src/main/resources/theme/base/login/login-page-expired.ftl
+++ b/themes/src/main/resources/theme/base/login/login-page-expired.ftl
@@ -6,7 +6,8 @@
         ${msg("pageExpiredTitle")}
     <#elseif section = "form">
         <p id="instruction1" class="instruction">
-            ${msg("pageExpiredMsg1")} <a href="${url.loginRestartFlowUrl}">${msg("doClickHere")}</a> . ${msg("pageExpiredMsg2")} <a href="${url.loginAction}">${msg("doClickHere")}</a> .
+            ${msg("pageExpiredMsg1")} <a id="loginRestartLink" href="${url.loginRestartFlowUrl}">${msg("doClickHere")}</a> .
+            ${msg("pageExpiredMsg2")} <a id="loginContinueLink" href="${url.loginAction}">${msg("doClickHere")}</a> .
         </p>
     </#if>
 </@layout.registrationLayout>
\ No newline at end of file
diff --git a/themes/src/main/resources/theme/base/login/messages/messages_en.properties b/themes/src/main/resources/theme/base/login/messages/messages_en.properties
index 994caa7..3d55559 100755
--- a/themes/src/main/resources/theme/base/login/messages/messages_en.properties
+++ b/themes/src/main/resources/theme/base/login/messages/messages_en.properties
@@ -127,6 +127,7 @@ invalidEmailMessage=Invalid email address.
 accountDisabledMessage=Account is disabled, contact admin.
 accountTemporarilyDisabledMessage=Account is temporarily disabled, contact admin or try again later.
 expiredCodeMessage=Login timeout. Please login again.
+expiredActionMessage=Action expired. Please continue with login now.
 
 missingFirstNameMessage=Please specify first name.
 missingLastNameMessage=Please specify last name.
@@ -213,6 +214,7 @@ realmSupportsNoCredentialsMessage=Realm does not support any credential type.
 identityProviderNotUniqueMessage=Realm supports multiple identity providers. Could not determine which identity provider should be used to authenticate with.
 emailVerifiedMessage=Your email address has been verified.
 staleEmailVerificationLink=The link you clicked is a old stale link and is no longer valid.  Maybe you have already verified your email?
+identityProviderAlreadyLinkedMessage=Federated identity returned by {0} is already linked to another user.
 
 locale_ca=Catal\u00E0
 locale_de=Deutsch
@@ -233,5 +235,7 @@ clientNotFoundMessage=Client not found.
 clientDisabledMessage=Client disabled.
 invalidParameterMessage=Invalid parameter\: {0}
 alreadyLoggedIn=You are already logged in.
+differentUserAuthenticated=You are already authenticated as different user ''{0}'' in this session. Please logout first.
+brokerLinkingSessionExpired=Requested broker account linking, but current session is no longer valid.
 
 p3pPolicy=CP="This is not a P3P policy!"