keycloak-aplcache

KEYCLOAK-4626 Authentication sessions - SAML, offline tokens,

4/21/2017 9:24:47 AM

Changes

model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/ClientSessionAdapter.java 292(+0 -292)

model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/ClientSessionEntity.java 152(+0 -152)

model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/mapreduce/ClientSessionMapper.java 129(+0 -129)

model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/mapreduce/ClientSessionsOfUserSessionMapper.java 77(+0 -77)

model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/stream/ClientSessionPredicate.java 114(+0 -114)

Details

diff --git a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/HttpClientBuilder.java b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/HttpClientBuilder.java
index e6d6588..38fa9d3 100755
--- a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/HttpClientBuilder.java
+++ b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/HttpClientBuilder.java
@@ -170,8 +170,8 @@ public class HttpClientBuilder {
         return this;
     }
 
-    public HttpClientBuilder disableCookieCache() {
-        this.disableCookieCache = true;
+    public HttpClientBuilder disableCookieCache(boolean disable) {
+        this.disableCookieCache = disable;
         return this;
     }
 
@@ -334,7 +334,7 @@ public class HttpClientBuilder {
     }
 
     public HttpClient build(AdapterHttpClientConfig adapterConfig) {
-        disableCookieCache(); // disable cookie cache as we don't want sticky sessions for load balancing
+        disableCookieCache(true); // disable cookie cache as we don't want sticky sessions for load balancing
 
         String truststorePath = adapterConfig.getTruststore();
         if (truststorePath != null) {
diff --git a/adapters/oidc/servlet-filter/src/main/java/org/keycloak/adapters/servlet/OIDCFilterSessionStore.java b/adapters/oidc/servlet-filter/src/main/java/org/keycloak/adapters/servlet/OIDCFilterSessionStore.java
index e28500b..e03158f 100755
--- a/adapters/oidc/servlet-filter/src/main/java/org/keycloak/adapters/servlet/OIDCFilterSessionStore.java
+++ b/adapters/oidc/servlet-filter/src/main/java/org/keycloak/adapters/servlet/OIDCFilterSessionStore.java
@@ -168,7 +168,7 @@ public class OIDCFilterSessionStore extends FilterSessionStore implements Adapte
         HttpSession httpSession = request.getSession();
         httpSession.setAttribute(KeycloakAccount.class.getName(), sAccount);
         httpSession.setAttribute(KeycloakSecurityContext.class.getName(), sAccount.getKeycloakSecurityContext());
-        if (idMapper != null) idMapper.map(account.getKeycloakSecurityContext().getToken().getClientSession(),  account.getPrincipal().getName(), httpSession.getId());
+        if (idMapper != null) idMapper.map(account.getKeycloakSecurityContext().getToken().getSessionState(),  account.getPrincipal().getName(), httpSession.getId());
         //String username = securityContext.getToken().getSubject();
         //log.fine("userSessionManagement.login: " + username);
     }
diff --git a/core/src/main/java/org/keycloak/representations/AccessToken.java b/core/src/main/java/org/keycloak/representations/AccessToken.java
index 4ef6831..36778e1 100755
--- a/core/src/main/java/org/keycloak/representations/AccessToken.java
+++ b/core/src/main/java/org/keycloak/representations/AccessToken.java
@@ -97,9 +97,6 @@ public class AccessToken extends IDToken {
         }
     }
 
-    @JsonProperty("client_session")
-    protected String clientSession;
-
     @JsonProperty("trusted-certs")
     protected Set<String> trustedCertificates;
 
@@ -156,10 +153,6 @@ public class AccessToken extends IDToken {
         return resourceAccess.get(resource);
     }
 
-    public String getClientSession() {
-        return clientSession;
-    }
-
     public Access addAccess(String service) {
         Access access = resourceAccess.get(service);
         if (access != null) return access;
@@ -168,11 +161,6 @@ public class AccessToken extends IDToken {
         return access;
     }
 
-    public AccessToken clientSession(String session) {
-        this.clientSession = session;
-        return this;
-    }
-
     @Override
     public AccessToken id(String id) {
         return (AccessToken) super.id(id);
diff --git a/core/src/main/java/org/keycloak/representations/RefreshToken.java b/core/src/main/java/org/keycloak/representations/RefreshToken.java
index 4b89cf6..3a7a951 100755
--- a/core/src/main/java/org/keycloak/representations/RefreshToken.java
+++ b/core/src/main/java/org/keycloak/representations/RefreshToken.java
@@ -40,7 +40,6 @@ public class RefreshToken extends AccessToken {
      */
     public RefreshToken(AccessToken token) {
         this();
-        this.clientSession = token.getClientSession();
         this.issuer = token.issuer;
         this.subject = token.subject;
         this.issuedFor = token.issuedFor;
diff --git a/examples/kerberos/README.md b/examples/kerberos/README.md
index 02bffdd..2c1d335 100644
--- a/examples/kerberos/README.md
+++ b/examples/kerberos/README.md
@@ -47,7 +47,7 @@ is in your `/etc/hosts` before other records for the 127.0.0.1 host to avoid iss
 
 **5)** Configure Kerberos client (On linux it's in file `/etc/krb5.conf` ). You need to configure `KEYCLOAK.ORG` realm for host `localhost` and enable `forwardable` flag, which is needed 
 for credential delegation example, as application needs to forward Kerberos ticket and authenticate with it against LDAP server. 
-See [this file](https://github.com/keycloak/keycloak/blob/master/testsuite/integration/src/test/resources/kerberos/test-krb5.conf) for inspiration.
+See [this file](https://github.com/keycloak/keycloak/blob/master/testsuite/integration-arquillian/tests/base/src/test/resources/kerberos/test-krb5.conf) for inspiration.
 On OS X the file to edit (or create) is `/Library/Preferences/edu.mit.Kerberos` with the same syntax as `krb5.conf`.
 On Windows the file to edit (or create) is `c:\Windows\krb5.ini` with the same syntax as `krb5.conf`.
 
diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/AuthenticatedClientSessionAdapter.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/AuthenticatedClientSessionAdapter.java
index 6eaba69..546e86f 100644
--- a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/AuthenticatedClientSessionAdapter.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/AuthenticatedClientSessionAdapter.java
@@ -27,7 +27,7 @@ import org.keycloak.models.AuthenticatedClientSessionModel;
 import org.keycloak.models.ClientModel;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.UserSessionModel;
-import org.keycloak.models.sessions.infinispan.entities.ClientLoginSessionEntity;
+import org.keycloak.models.sessions.infinispan.entities.AuthenticatedClientSessionEntity;
 import org.keycloak.models.sessions.infinispan.entities.SessionEntity;
 import org.keycloak.models.sessions.infinispan.entities.UserSessionEntity;
 
@@ -36,14 +36,16 @@ import org.keycloak.models.sessions.infinispan.entities.UserSessionEntity;
  */
 public class AuthenticatedClientSessionAdapter implements AuthenticatedClientSessionModel {
 
-    private final ClientLoginSessionEntity entity;
+    private final AuthenticatedClientSessionEntity entity;
+    private final ClientModel client;
     private final InfinispanUserSessionProvider provider;
     private final Cache<String, SessionEntity> cache;
     private UserSessionAdapter userSession;
 
-    public AuthenticatedClientSessionAdapter(ClientLoginSessionEntity entity, UserSessionAdapter userSession, InfinispanUserSessionProvider provider, Cache<String, SessionEntity> cache) {
+    public AuthenticatedClientSessionAdapter(AuthenticatedClientSessionEntity entity, ClientModel client, UserSessionAdapter userSession, InfinispanUserSessionProvider provider, Cache<String, SessionEntity> cache) {
         this.provider = provider;
         this.entity = entity;
+        this.client = client;
         this.cache = cache;
         this.userSession = userSession;
     }
@@ -55,23 +57,23 @@ public class AuthenticatedClientSessionAdapter implements AuthenticatedClientSes
 
     @Override
     public void setUserSession(UserSessionModel userSession) {
-        String clientUUID = entity.getClient();
+        String clientUUID = client.getId();
         UserSessionEntity sessionEntity = this.userSession.getEntity();
 
         // Dettach userSession
         if (userSession == null) {
-            if (sessionEntity.getClientLoginSessions() != null) {
-                sessionEntity.getClientLoginSessions().remove(clientUUID);
+            if (sessionEntity.getAuthenticatedClientSessions() != null) {
+                sessionEntity.getAuthenticatedClientSessions().remove(clientUUID);
                 update();
                 this.userSession = null;
             }
         } else {
             this.userSession = (UserSessionAdapter) userSession;
 
-            if (sessionEntity.getClientLoginSessions() == null) {
-                sessionEntity.setClientLoginSessions(new HashMap<>());
+            if (sessionEntity.getAuthenticatedClientSessions() == null) {
+                sessionEntity.setAuthenticatedClientSessions(new HashMap<>());
             }
-            sessionEntity.getClientLoginSessions().put(clientUUID, entity);
+            sessionEntity.getAuthenticatedClientSessions().put(clientUUID, entity);
             update();
         }
     }
@@ -104,8 +106,7 @@ public class AuthenticatedClientSessionAdapter implements AuthenticatedClientSes
 
     @Override
     public ClientModel getClient() {
-        String client = entity.getClient();
-        return getRealm().getClientById(client);
+        return client;
     }
 
     @Override
@@ -192,4 +193,5 @@ public class AuthenticatedClientSessionAdapter implements AuthenticatedClientSes
         copy.putAll(entity.getNotes());
         return copy;
     }
+
 }
diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/Consumers.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/Consumers.java
index e55cf31..19cb7c7 100644
--- a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/Consumers.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/Consumers.java
@@ -19,11 +19,9 @@ package org.keycloak.models.sessions.infinispan;
 
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.UserSessionModel;
-import org.keycloak.models.sessions.infinispan.entities.ClientSessionEntity;
 import org.keycloak.models.sessions.infinispan.entities.SessionEntity;
 import org.keycloak.models.sessions.infinispan.entities.UserSessionEntity;
 
-import java.util.HashMap;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
@@ -41,21 +39,6 @@ public class Consumers {
         return new UserSessionModelsConsumer(provider, realm, offline);
     }
 
-    public static class UserSessionIdAndTimestampConsumer implements Consumer<Map.Entry<String, SessionEntity>> {
-
-        private Map<String, Integer> sessions = new HashMap<>();
-
-        @Override
-        public void accept(Map.Entry<String, SessionEntity> entry) {
-            SessionEntity e = entry.getValue();
-            if (e instanceof ClientSessionEntity) {
-                ClientSessionEntity ce = (ClientSessionEntity) e;
-                sessions.put(ce.getUserSession(), ce.getTimestamp());
-            }
-        }
-
-    }
-
     public static class UserSessionModelsConsumer implements Consumer<Map.Entry<String, SessionEntity>> {
 
         private InfinispanUserSessionProvider provider;
diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/UserSessionEntity.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/UserSessionEntity.java
index 3a5a4eb..54d182f 100755
--- a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/UserSessionEntity.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/UserSessionEntity.java
@@ -46,13 +46,11 @@ public class UserSessionEntity extends SessionEntity {
 
     private int lastSessionRefresh;
 
-    private Set<String> clientSessions = new CopyOnWriteArraySet<>();
-
     private UserSessionModel.State state;
 
     private Map<String, String> notes = new ConcurrentHashMap<>();
 
-    private Map<String, ClientLoginSessionEntity> clientLoginSessions;
+    private Map<String, AuthenticatedClientSessionEntity> authenticatedClientSessions;
 
     public String getUser() {
         return user;
@@ -110,10 +108,6 @@ public class UserSessionEntity extends SessionEntity {
         this.lastSessionRefresh = lastSessionRefresh;
     }
 
-    public Set<String> getClientSessions() {
-        return clientSessions;
-    }
-
     public Map<String, String> getNotes() {
         return notes;
     }
@@ -122,12 +116,12 @@ public class UserSessionEntity extends SessionEntity {
         this.notes = notes;
     }
 
-    public Map<String, ClientLoginSessionEntity> getClientLoginSessions() {
-        return clientLoginSessions;
+    public Map<String, AuthenticatedClientSessionEntity> getAuthenticatedClientSessions() {
+        return authenticatedClientSessions;
     }
 
-    public void setClientLoginSessions(Map<String, ClientLoginSessionEntity> clientLoginSessions) {
-        this.clientLoginSessions = clientLoginSessions;
+    public void setAuthenticatedClientSessions(Map<String, AuthenticatedClientSessionEntity> authenticatedClientSessions) {
+        this.authenticatedClientSessions = authenticatedClientSessions;
     }
 
     public UserSessionModel.State getState() {
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 1e23524..a802544 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
@@ -120,6 +120,7 @@ public class InfinispanAuthenticationSessionProvider implements AuthenticationSe
         log.debugf("Removed %d expired user sessions for realm '%s'", counter, realm.getName());
     }
 
+    // TODO: Should likely listen to "RealmRemovedEvent" received from cluster and clean just local sessions
     @Override
     public void onRealmRemoved(RealmModel realm) {
         Iterator<Map.Entry<String, AuthenticationSessionEntity>> itr = cache.entrySet().stream().filter(AuthenticationSessionPredicate.create(realm.getId())).iterator();
@@ -128,6 +129,7 @@ public class InfinispanAuthenticationSessionProvider implements AuthenticationSe
         }
     }
 
+    // TODO: Should likely listen to "ClientRemovedEvent" received from cluster and clean just local sessions
     @Override
     public void onClientRemoved(RealmModel realm, ClientModel client) {
         Iterator<Map.Entry<String, AuthenticationSessionEntity>> itr = cache.entrySet().stream().filter(AuthenticationSessionPredicate.create(realm.getId()).client(client.getId())).iterator();
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 e87347e..0e50a73 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
@@ -25,9 +25,7 @@ import org.keycloak.common.util.Time;
 import org.keycloak.models.ClientInitialAccessModel;
 import org.keycloak.models.AuthenticatedClientSessionModel;
 import org.keycloak.models.ClientModel;
-import org.keycloak.models.ClientSessionModel;
 import org.keycloak.models.KeycloakSession;
-import org.keycloak.models.KeycloakTransaction;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.UserLoginFailureModel;
 import org.keycloak.models.UserModel;
@@ -35,31 +33,29 @@ import org.keycloak.models.UserSessionModel;
 import org.keycloak.models.UserSessionProvider;
 import org.keycloak.models.session.UserSessionPersisterProvider;
 import org.keycloak.models.sessions.infinispan.entities.ClientInitialAccessEntity;
-import org.keycloak.models.sessions.infinispan.entities.ClientLoginSessionEntity;
-import org.keycloak.models.sessions.infinispan.entities.ClientSessionEntity;
+import org.keycloak.models.sessions.infinispan.entities.AuthenticatedClientSessionEntity;
 import org.keycloak.models.sessions.infinispan.entities.LoginFailureEntity;
 import org.keycloak.models.sessions.infinispan.entities.LoginFailureKey;
 import org.keycloak.models.sessions.infinispan.entities.SessionEntity;
 import org.keycloak.models.sessions.infinispan.entities.UserSessionEntity;
 import org.keycloak.models.sessions.infinispan.stream.ClientInitialAccessPredicate;
-import org.keycloak.models.sessions.infinispan.stream.ClientSessionPredicate;
 import org.keycloak.models.sessions.infinispan.stream.Comparators;
 import org.keycloak.models.sessions.infinispan.stream.Mappers;
 import org.keycloak.models.sessions.infinispan.stream.SessionPredicate;
 import org.keycloak.models.sessions.infinispan.stream.UserLoginFailurePredicate;
 import org.keycloak.models.sessions.infinispan.stream.UserSessionPredicate;
 import org.keycloak.models.utils.KeycloakModelUtils;
-import org.keycloak.models.utils.RealmInfoUtil;
 
 import java.util.Collection;
+import java.util.Collections;
 import java.util.HashMap;
-import java.util.HashSet;
 import java.util.Iterator;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 import java.util.function.Consumer;
 import java.util.function.Predicate;
+import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
 /**
@@ -90,31 +86,11 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
         return offline ? offlineSessionCache : sessionCache;
     }
 
-
-    // TODO:mposolda remove
-    @Override
-    public ClientSessionModel createClientSession(RealmModel realm, ClientModel client) {
-        String id = KeycloakModelUtils.generateId();
-
-        ClientSessionEntity entity = new ClientSessionEntity();
-        entity.setId(id);
-        entity.setRealm(realm.getId());
-        entity.setTimestamp(Time.currentTime());
-        entity.setClient(client.getId());
-
-
-        tx.put(sessionCache, id, entity);
-
-        ClientSessionAdapter wrap = wrap(realm, entity, false);
-        return wrap;
-    }
-
     @Override
     public AuthenticatedClientSessionModel createClientSession(RealmModel realm, ClientModel client, UserSessionModel userSession) {
-        ClientLoginSessionEntity entity = new ClientLoginSessionEntity();
-        entity.setClient(client.getId());
+        AuthenticatedClientSessionEntity entity = new AuthenticatedClientSessionEntity();
 
-        AuthenticatedClientSessionAdapter adapter = new AuthenticatedClientSessionAdapter(entity, (UserSessionAdapter) userSession, this, sessionCache);
+        AuthenticatedClientSessionAdapter adapter = new AuthenticatedClientSessionAdapter(entity, client, (UserSessionAdapter) userSession, this, sessionCache);
         adapter.setUserSession(userSession);
         return adapter;
     }
@@ -123,6 +99,15 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
     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);
+
+        updateSessionEntity(entity, realm, user, loginUsername, ipAddress, authMethod, rememberMe, brokerSessionId, brokerUserId);
+
+        tx.putIfAbsent(sessionCache, id, entity);
+
+        return wrap(realm, entity, false);
+    }
+
+    void updateSessionEntity(UserSessionEntity entity, RealmModel realm, UserModel user, String loginUsername, String ipAddress, String authMethod, boolean rememberMe, String brokerSessionId, String brokerUserId) {
         entity.setRealm(realm.getId());
         entity.setUser(user.getId());
         entity.setLoginUsername(loginUsername);
@@ -137,41 +122,7 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
         entity.setStarted(currentTime);
         entity.setLastSessionRefresh(currentTime);
 
-        tx.putIfAbsent(sessionCache, id, entity);
 
-        return wrap(realm, entity, false);
-    }
-
-    @Override
-    public ClientSessionModel getClientSession(RealmModel realm, String id) {
-        return getClientSession(realm, id, false);
-    }
-
-    protected ClientSessionModel getClientSession(RealmModel realm, String id, boolean offline) {
-        Cache<String, SessionEntity> cache = getCache(offline);
-        ClientSessionEntity entity = (ClientSessionEntity) tx.get(cache, id); // Chance created in this transaction
-
-        if (entity == null) {
-            entity = (ClientSessionEntity) cache.get(id);
-        }
-
-        return wrap(realm, entity, offline);
-    }
-
-    @Override
-    public ClientSessionModel getClientSession(String id) {
-        // Chance created in this transaction
-        ClientSessionEntity entity = (ClientSessionEntity) tx.get(sessionCache, id);
-
-        if (entity == null) {
-            entity = (ClientSessionEntity) sessionCache.get(id);
-        }
-
-        if (entity != null) {
-            RealmModel realm = session.realms().getRealm(entity.getRealm());
-            return wrap(realm, entity, false);
-        }
-        return null;
     }
 
     @Override
@@ -230,37 +181,50 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
     protected List<UserSessionModel> getUserSessions(final RealmModel realm, ClientModel client, int firstResult, int maxResults, final boolean offline) {
         final Cache<String, SessionEntity> cache = getCache(offline);
 
-        Iterator<UserSessionTimestamp> itr = cache.entrySet().stream()
-                .filter(ClientSessionPredicate.create(realm.getId()).client(client.getId()).requireUserSession())
-                .map(Mappers.clientSessionToUserSessionTimestamp())
-                .iterator();
-
-        Map<String, UserSessionTimestamp> m = new HashMap<>();
-        while(itr.hasNext()) {
-            UserSessionTimestamp next = itr.next();
-            if (!m.containsKey(next.getUserSessionId()) || m.get(next.getUserSessionId()).getClientSessionTimestamp() < next.getClientSessionTimestamp()) {
-                m.put(next.getUserSessionId(), next);
-            }
+        Stream<UserSessionEntity> stream = cache.entrySet().stream()
+                .filter(UserSessionPredicate.create(realm.getId()).client(client.getId()))
+                .map(Mappers.userSessionEntity())
+                .sorted(Comparators.userSessionLastSessionRefresh());
+
+        // Doesn't work due to ISPN-6575 . TODO Fix once infinispan upgraded to 8.2.2.Final or 9.0
+//        if (firstResult > 0) {
+//            stream = stream.skip(firstResult);
+//        }
+//
+//        if (maxResults > 0) {
+//            stream = stream.limit(maxResults);
+//        }
+//
+//      List<UserSessionEntity> entities = stream.collect(Collectors.toList());
+
+
+        // Workaround for ISPN-6575 TODO Fix once infinispan upgraded to 8.2.2.Final or 9.0 and replace with the more effective code above
+        if (firstResult < 0) {
+            firstResult = 0;
+        }
+        if (maxResults < 0) {
+            maxResults = Integer.MAX_VALUE;
         }
 
-        Stream<UserSessionTimestamp> stream = new LinkedList<>(m.values()).stream().sorted(Comparators.userSessionTimestamp());
-
-        if (firstResult > 0) {
-            stream = stream.skip(firstResult);
+        int count = firstResult + maxResults;
+        if (count > 0) {
+            stream = stream.limit(count);
         }
+        List<UserSessionEntity> entities = stream.collect(Collectors.toList());
 
-        if (maxResults > 0) {
-            stream = stream.limit(maxResults);
+        if (firstResult > entities.size()) {
+            return Collections.emptyList();
         }
 
+        maxResults = Math.min(maxResults, entities.size() - firstResult);
+        entities = entities.subList(firstResult, firstResult + maxResults);
+
+
         final List<UserSessionModel> sessions = new LinkedList<>();
-        stream.forEach(new Consumer<UserSessionTimestamp>() {
+        entities.stream().forEach(new Consumer<UserSessionEntity>() {
             @Override
-            public void accept(UserSessionTimestamp userSessionTimestamp) {
-                SessionEntity entity = cache.get(userSessionTimestamp.getUserSessionId());
-                if (entity != null) {
-                    sessions.add(wrap(realm, (UserSessionEntity) entity, offline));
-                }
+            public void accept(UserSessionEntity userSessionEntity) {
+                sessions.add(wrap(realm, userSessionEntity, offline));
             }
         });
 
@@ -273,7 +237,9 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
     }
 
     protected long getUserSessionsCount(RealmModel realm, ClientModel client, boolean offline) {
-        return getCache(offline).entrySet().stream().filter(ClientSessionPredicate.create(realm.getId()).client(client.getId()).requireUserSession()).map(Mappers.clientSessionToUserSessionId()).distinct().count();
+        return getCache(offline).entrySet().stream()
+                .filter(UserSessionPredicate.create(realm.getId()).client(client.getId()))
+                .count();
     }
 
     @Override
@@ -303,9 +269,7 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
     public void removeExpired(RealmModel realm) {
         log.debugf("Removing expired sessions");
         removeExpiredUserSessions(realm);
-        removeExpiredClientSessions(realm);
         removeExpiredOfflineUserSessions(realm);
-        removeExpiredOfflineClientSessions(realm);
         removeExpiredClientInitialAccess(realm);
     }
 
@@ -322,33 +286,11 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
             counter++;
             UserSessionEntity entity = (UserSessionEntity) itr.next().getValue();
             tx.remove(sessionCache, entity.getId());
-
-            if (entity.getClientSessions() != null) {
-                for (String clientSessionId : entity.getClientSessions()) {
-                    tx.remove(sessionCache, clientSessionId);
-                }
-            }
         }
 
         log.debugf("Removed %d expired user sessions for realm '%s'", counter, realm.getName());
     }
 
-    private void removeExpiredClientSessions(RealmModel realm) {
-        int expiredDettachedClientSession = Time.currentTime() - RealmInfoUtil.getDettachedClientSessionLifespan(realm);
-
-        // Each cluster node cleanups just local sessions, which are those owned by himself (+ few more taking l1 cache into account)
-        Iterator<Map.Entry<String, SessionEntity>> itr = sessionCache.getAdvancedCache().withFlags(Flag.CACHE_MODE_LOCAL)
-                .entrySet().stream().filter(ClientSessionPredicate.create(realm.getId()).expiredRefresh(expiredDettachedClientSession).requireNullUserSession()).iterator();
-
-        int counter = 0;
-        while (itr.hasNext()) {
-            counter++;
-            tx.remove(sessionCache, itr.next().getKey());
-        }
-
-        log.debugf("Removed %d expired client sessions for realm '%s'", counter, realm.getName());
-    }
-
     private void removeExpiredOfflineUserSessions(RealmModel realm) {
         UserSessionPersisterProvider persister = session.getProvider(UserSessionPersisterProvider.class);
         int expiredOffline = Time.currentTime() - realm.getOfflineSessionIdleTimeout();
@@ -366,33 +308,14 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
 
             persister.removeUserSession(entity.getId(), true);
 
-            for (String clientSessionId : entity.getClientSessions()) {
-                tx.remove(offlineSessionCache, clientSessionId);
+            for (String clientUUID : entity.getAuthenticatedClientSessions().keySet()) {
+                persister.removeClientSession(entity.getId(), clientUUID, true);
             }
         }
 
         log.debugf("Removed %d expired offline user sessions for realm '%s'", counter, realm.getName());
     }
 
-    private void removeExpiredOfflineClientSessions(RealmModel realm) {
-        UserSessionPersisterProvider persister = session.getProvider(UserSessionPersisterProvider.class);
-        int expiredOffline = Time.currentTime() - realm.getOfflineSessionIdleTimeout();
-
-        // Each cluster node cleanups just local sessions, which are those owned by himself (+ few more taking l1 cache into account)
-        Iterator<String> itr = offlineSessionCache.getAdvancedCache().withFlags(Flag.CACHE_MODE_LOCAL)
-                .entrySet().stream().filter(ClientSessionPredicate.create(realm.getId()).expiredRefresh(expiredOffline)).map(Mappers.sessionId()).iterator();
-
-        int counter = 0;
-        while (itr.hasNext()) {
-            counter++;
-            String sessionId = itr.next();
-            tx.remove(offlineSessionCache, sessionId);
-            persister.removeClientSession(sessionId, true);
-        }
-
-        log.debugf("Removed %d expired offline client sessions for realm '%s'", counter, realm.getName());
-    }
-
     private void removeExpiredClientInitialAccess(RealmModel realm) {
         Iterator<String> itr = sessionCache.getAdvancedCache().withFlags(Flag.CACHE_MODE_LOCAL)
                 .entrySet().stream().filter(ClientInitialAccessPredicate.create(realm.getId()).expired(Time.currentTime())).map(Mappers.sessionId()).iterator();
@@ -454,21 +377,7 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
 
     @Override
     public void onClientRemoved(RealmModel realm, ClientModel client) {
-        onClientRemoved(realm, client, true);
-        onClientRemoved(realm, client, false);
-    }
-
-    private void onClientRemoved(RealmModel realm, ClientModel client, boolean offline) {
-        Cache<String, SessionEntity> cache = getCache(offline);
-
-        Iterator<Map.Entry<String, SessionEntity>> itr = cache.entrySet().stream().filter(ClientSessionPredicate.create(realm.getId()).client(client.getId())).iterator();
-        while (itr.hasNext()) {
-            ClientSessionEntity entity = (ClientSessionEntity) itr.next().getValue();
-            ClientSessionAdapter adapter = wrap(realm, entity, offline);
-            adapter.setUserSession(null);
-
-            tx.remove(cache, entity.getId());
-        }
+        // Nothing for now. userSession.getAuthenticatedClientSessions() will check lazily if particular client exists and update userSession on-the-fly.
     }
 
 
@@ -484,55 +393,10 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
     public void close() {
     }
 
-    void attachSession(UserSessionAdapter userSession, ClientSessionModel clientSession) {
-        UserSessionEntity entity = userSession.getEntity();
-        String clientSessionId = clientSession.getId();
-        if (!entity.getClientSessions().contains(clientSessionId)) {
-            entity.getClientSessions().add(clientSessionId);
-            userSession.update();
-        }
-    }
-
-    @Override
-    public void removeClientSession(RealmModel realm, ClientSessionModel clientSession) {
-        removeClientSession(realm, clientSession, false);
-    }
-
-    protected void removeClientSession(RealmModel realm, ClientSessionModel clientSession, boolean offline) {
-        Cache<String, SessionEntity> cache = getCache(offline);
-
-        UserSessionModel userSession = clientSession.getUserSession();
-        if (userSession != null)  {
-            UserSessionEntity entity = ((UserSessionAdapter) userSession).getEntity();
-            if (entity.getClientSessions() != null) {
-                entity.getClientSessions().remove(clientSession.getId());
-
-            }
-            tx.replace(cache, entity.getId(), entity);
-        }
-        tx.remove(cache, clientSession.getId());
-    }
-
-
-    void dettachSession(UserSessionAdapter userSession, ClientSessionModel clientSession) {
-        UserSessionEntity entity = userSession.getEntity();
-        String clientSessionId = clientSession.getId();
-        if (entity.getClientSessions() != null && entity.getClientSessions().contains(clientSessionId)) {
-            entity.getClientSessions().remove(clientSessionId);
-            userSession.update();
-        }
-    }
-
     protected void removeUserSession(RealmModel realm, UserSessionEntity sessionEntity, boolean offline) {
         Cache<String, SessionEntity> cache = getCache(offline);
 
         tx.remove(cache, sessionEntity.getId());
-
-        if (sessionEntity.getClientSessions() != null) {
-            for (String clientSessionId : sessionEntity.getClientSessions()) {
-                tx.remove(cache, clientSessionId);
-            }
-        }
     }
 
     InfinispanKeycloakTransaction getTx() {
@@ -560,11 +424,6 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
         return models;
     }
 
-    ClientSessionAdapter wrap(RealmModel realm, ClientSessionEntity entity, boolean offline) {
-        Cache<String, SessionEntity> cache = getCache(offline);
-        return entity != null ? new ClientSessionAdapter(session, this, cache, realm, entity, offline) : null;
-    }
-
     ClientInitialAccessAdapter wrap(RealmModel realm, ClientInitialAccessEntity entity) {
         Cache<String, SessionEntity> cache = getCache(false);
         return entity != null ? new ClientInitialAccessAdapter(session, this, cache, realm, entity) : null;
@@ -574,14 +433,6 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
         return entity != null ? new UserLoginFailureAdapter(this, loginFailureCache, key, entity) : null;
     }
 
-    List<ClientSessionModel> wrapClientSessions(RealmModel realm, Collection<ClientSessionEntity> entities, boolean offline) {
-        List<ClientSessionModel> models = new LinkedList<>();
-        for (ClientSessionEntity e : entities) {
-            models.add(wrap(realm, e, offline));
-        }
-        return models;
-    }
-
     UserSessionEntity getUserSessionEntity(UserSessionModel userSession, boolean offline) {
         if (userSession instanceof UserSessionAdapter) {
             return ((UserSessionAdapter) userSession).getEntity();
@@ -594,7 +445,7 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
 
     @Override
     public UserSessionModel createOfflineUserSession(UserSessionModel userSession) {
-        UserSessionAdapter offlineUserSession = importUserSession(userSession, true);
+        UserSessionAdapter offlineUserSession = importUserSession(userSession, true, false);
 
         // started and lastSessionRefresh set to current time
         int currentTime = Time.currentTime();
@@ -605,7 +456,7 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
     }
 
     @Override
-    public UserSessionModel getOfflineUserSession(RealmModel realm, String userSessionId) {
+    public UserSessionAdapter getOfflineUserSession(RealmModel realm, String userSessionId) {
         return getUserSession(realm, userSessionId, true);
     }
 
@@ -617,26 +468,19 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
         }
     }
 
-    // TODO:mposolda
-    /*
+
+
     @Override
-    public ClientSessionModel createOfflineClientSession(ClientSessionModel clientSession) {
-        ClientSessionAdapter offlineClientSession = importClientSession(clientSession, true);
+    public AuthenticatedClientSessionModel createOfflineClientSession(AuthenticatedClientSessionModel clientSession, UserSessionModel offlineUserSession) {
+        UserSessionAdapter userSessionAdapter = (offlineUserSession instanceof UserSessionAdapter) ? (UserSessionAdapter) offlineUserSession :
+                getOfflineUserSession(offlineUserSession.getRealm(), offlineUserSession.getId());
+
+        AuthenticatedClientSessionAdapter offlineClientSession = importClientSession(userSessionAdapter, clientSession);
 
         // update timestamp to current time
         offlineClientSession.setTimestamp(Time.currentTime());
 
         return offlineClientSession;
-    }*/
-
-    @Override
-    public AuthenticatedClientSessionModel createOfflineClientSession(AuthenticatedClientSessionModel clientSession) {
-        return null;
-    }
-
-    @Override
-    public ClientSessionModel getOfflineClientSession(RealmModel realm, String clientSessionId) {
-        return getClientSession(realm, clientSessionId, true);
     }
 
     @Override
@@ -654,12 +498,6 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
     }
 
     @Override
-    public void removeOfflineClientSession(RealmModel realm, String clientSessionId) {
-        ClientSessionModel clientSession = getOfflineClientSession(realm, clientSessionId);
-        removeClientSession(realm, clientSession, true);
-    }
-
-    @Override
     public long getOfflineSessionsCount(RealmModel realm, ClientModel client) {
         return getUserSessionsCount(realm, client, true);
     }
@@ -670,7 +508,7 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
     }
 
     @Override
-    public UserSessionAdapter importUserSession(UserSessionModel userSession, boolean offline) {
+    public UserSessionAdapter importUserSession(UserSessionModel userSession, boolean offline, boolean importAuthenticatedClientSessions) {
         UserSessionEntity entity = new UserSessionEntity();
         entity.setId(userSession.getId());
         entity.setRealm(userSession.getRealm().getId());
@@ -688,34 +526,45 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
         entity.setStarted(userSession.getStarted());
         entity.setLastSessionRefresh(userSession.getLastSessionRefresh());
 
+
         Cache<String, SessionEntity> cache = getCache(offline);
         tx.put(cache, userSession.getId(), entity);
-        return wrap(userSession.getRealm(), entity, offline);
+        UserSessionAdapter importedSession = wrap(userSession.getRealm(), entity, offline);
+
+        // Handle client sessions
+        if (importAuthenticatedClientSessions) {
+            for (AuthenticatedClientSessionModel clientSession : userSession.getAuthenticatedClientSessions().values()) {
+                importClientSession(importedSession, clientSession);
+            }
+        }
+
+        return importedSession;
     }
 
-    @Override
-    public ClientSessionAdapter importClientSession(ClientSessionModel clientSession, boolean offline) {
-        ClientSessionEntity entity = new ClientSessionEntity();
-        entity.setId(clientSession.getId());
-        entity.setRealm(clientSession.getRealm().getId());
+
+    private AuthenticatedClientSessionAdapter importClientSession(UserSessionAdapter importedUserSession, AuthenticatedClientSessionModel clientSession) {
+        AuthenticatedClientSessionEntity entity = new AuthenticatedClientSessionEntity();
 
         entity.setAction(clientSession.getAction());
-        entity.setAuthenticatorStatus(clientSession.getExecutionStatus());
         entity.setAuthMethod(clientSession.getProtocol());
-        if (clientSession.getAuthenticatedUser() != null) {
-            entity.setAuthUserId(clientSession.getAuthenticatedUser().getId());
-        }
-        entity.setClient(clientSession.getClient().getId());
+
         entity.setNotes(clientSession.getNotes());
         entity.setProtocolMappers(clientSession.getProtocolMappers());
         entity.setRedirectUri(clientSession.getRedirectUri());
         entity.setRoles(clientSession.getRoles());
         entity.setTimestamp(clientSession.getTimestamp());
-        entity.setUserSessionNotes(clientSession.getUserSessionNotes());
 
-        Cache<String, SessionEntity> cache = getCache(offline);
-        tx.put(cache, clientSession.getId(), entity);
-        return wrap(clientSession.getRealm(), entity, offline);
+        Map<String, AuthenticatedClientSessionEntity> clientSessions = importedUserSession.getEntity().getAuthenticatedClientSessions();
+        if (clientSessions == null) {
+            clientSessions = new HashMap<>();
+            importedUserSession.getEntity().setAuthenticatedClientSessions(clientSessions);
+        }
+
+        clientSessions.put(clientSession.getClient().getId(), entity);
+
+        importedUserSession.update();
+
+        return new AuthenticatedClientSessionAdapter(entity, clientSession.getClient(), importedUserSession, this, importedUserSession.getCache());
     }
 
     @Override
diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/initializer/OfflineUserSessionLoader.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/initializer/OfflineUserSessionLoader.java
index 83a3885..2b6fb71 100644
--- a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/initializer/OfflineUserSessionLoader.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/initializer/OfflineUserSessionLoader.java
@@ -19,7 +19,6 @@ package org.keycloak.models.sessions.infinispan.initializer;
 
 import org.jboss.logging.Logger;
 import org.keycloak.cluster.ClusterProvider;
-import org.keycloak.models.ClientSessionModel;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.UserSessionModel;
 import org.keycloak.models.session.UserSessionPersisterProvider;
@@ -64,12 +63,7 @@ public class OfflineUserSessionLoader implements SessionLoader {
         for (UserSessionModel persistentSession : sessions) {
 
             // Save to memory/infinispan
-            UserSessionModel offlineUserSession = session.sessions().importUserSession(persistentSession, true);
-
-            for (ClientSessionModel persistentClientSession : persistentSession.getClientSessions()) {
-                ClientSessionModel offlineClientSession = session.sessions().importClientSession(persistentClientSession, true);
-                offlineClientSession.setUserSession(offlineUserSession);
-            }
+            UserSessionModel offlineUserSession = session.sessions().importUserSession(persistentSession, true, true);
         }
 
         return true;
diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/stream/Comparators.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/stream/Comparators.java
index 4907ec1..ec2a2cb 100644
--- a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/stream/Comparators.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/stream/Comparators.java
@@ -18,6 +18,8 @@
 package org.keycloak.models.sessions.infinispan.stream;
 
 import org.keycloak.models.sessions.infinispan.UserSessionTimestamp;
+import org.keycloak.models.sessions.infinispan.entities.SessionEntity;
+import org.keycloak.models.sessions.infinispan.entities.UserSessionEntity;
 
 import java.io.Serializable;
 import java.util.Comparator;
@@ -38,4 +40,17 @@ public class Comparators {
         }
     }
 
+
+    public static Comparator<UserSessionEntity> userSessionLastSessionRefresh() {
+        return new UserSessionLastSessionRefreshComparator();
+    }
+
+    private static class UserSessionLastSessionRefreshComparator implements Comparator<UserSessionEntity>, Serializable {
+
+        @Override
+        public int compare(UserSessionEntity u1, UserSessionEntity u2) {
+            return u1.getLastSessionRefresh() - u2.getLastSessionRefresh();
+        }
+    }
+
 }
diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/stream/Mappers.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/stream/Mappers.java
index 6bf1358..dd2db68 100644
--- a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/stream/Mappers.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/stream/Mappers.java
@@ -18,10 +18,10 @@
 package org.keycloak.models.sessions.infinispan.stream;
 
 import org.keycloak.models.sessions.infinispan.UserSessionTimestamp;
-import org.keycloak.models.sessions.infinispan.entities.ClientSessionEntity;
 import org.keycloak.models.sessions.infinispan.entities.LoginFailureEntity;
 import org.keycloak.models.sessions.infinispan.entities.LoginFailureKey;
 import org.keycloak.models.sessions.infinispan.entities.SessionEntity;
+import org.keycloak.models.sessions.infinispan.entities.UserSessionEntity;
 
 import java.io.Serializable;
 import java.util.Map;
@@ -33,10 +33,6 @@ import java.util.function.Function;
  */
 public class Mappers {
 
-    public static Function<Map.Entry<String, SessionEntity>, UserSessionTimestamp> clientSessionToUserSessionTimestamp() {
-        return new ClientSessionToUserSessionTimestampMapper();
-    }
-
     public static Function<Map.Entry<String, Optional<UserSessionTimestamp>>, UserSessionTimestamp> userSessionTimestamp() {
         return new UserSessionTimestampMapper();
     }
@@ -49,21 +45,12 @@ public class Mappers {
         return new SessionEntityMapper();
     }
 
-    public static Function<Map.Entry<LoginFailureKey, LoginFailureEntity>, LoginFailureKey> loginFailureId() {
-        return new LoginFailureIdMapper();
+    public static Function<Map.Entry<String, SessionEntity>, UserSessionEntity> userSessionEntity() {
+        return new UserSessionEntityMapper();
     }
 
-    public static Function<Map.Entry<String, SessionEntity>, String> clientSessionToUserSessionId() {
-        return new ClientSessionToUserSessionIdMapper();
-    }
-
-    private static class ClientSessionToUserSessionTimestampMapper implements Function<Map.Entry<String, SessionEntity>, UserSessionTimestamp>, Serializable {
-        @Override
-        public UserSessionTimestamp apply(Map.Entry<String, SessionEntity> entry) {
-            SessionEntity e = entry.getValue();
-            ClientSessionEntity entity = (ClientSessionEntity) e;
-            return new UserSessionTimestamp(entity.getUserSession(), entity.getTimestamp());
-        }
+    public static Function<Map.Entry<LoginFailureKey, LoginFailureEntity>, LoginFailureKey> loginFailureId() {
+        return new LoginFailureIdMapper();
     }
 
     private static class UserSessionTimestampMapper implements Function<Map.Entry<String, Optional<org.keycloak.models.sessions.infinispan.UserSessionTimestamp>>, org.keycloak.models.sessions.infinispan.UserSessionTimestamp>, Serializable {
@@ -87,19 +74,18 @@ public class Mappers {
         }
     }
 
-    private static class LoginFailureIdMapper implements Function<Map.Entry<LoginFailureKey, LoginFailureEntity>, LoginFailureKey>, Serializable {
+    private static class UserSessionEntityMapper implements Function<Map.Entry<String, SessionEntity>, UserSessionEntity>, Serializable {
         @Override
-        public LoginFailureKey apply(Map.Entry<LoginFailureKey, LoginFailureEntity> entry) {
-            return entry.getKey();
+        public UserSessionEntity apply(Map.Entry<String, SessionEntity> entry) {
+            return (UserSessionEntity) entry.getValue();
         }
     }
 
-    private static class ClientSessionToUserSessionIdMapper implements Function<Map.Entry<String, SessionEntity>, String>, Serializable {
+    private static class LoginFailureIdMapper implements Function<Map.Entry<LoginFailureKey, LoginFailureEntity>, LoginFailureKey>, Serializable {
         @Override
-        public String apply(Map.Entry<String, SessionEntity> entry) {
-            SessionEntity e = entry.getValue();
-            ClientSessionEntity entity = (ClientSessionEntity) e;
-            return entity.getUserSession();
+        public LoginFailureKey apply(Map.Entry<LoginFailureKey, LoginFailureEntity> entry) {
+            return entry.getKey();
         }
     }
+
 }
diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/stream/UserSessionPredicate.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/stream/UserSessionPredicate.java
index 77ff572..0cc3fcc 100644
--- a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/stream/UserSessionPredicate.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/stream/UserSessionPredicate.java
@@ -33,6 +33,8 @@ public class UserSessionPredicate implements Predicate<Map.Entry<String, Session
 
     private String user;
 
+    private String client;
+
     private Integer expired;
 
     private Integer expiredRefresh;
@@ -53,6 +55,11 @@ public class UserSessionPredicate implements Predicate<Map.Entry<String, Session
         return this;
     }
 
+    public UserSessionPredicate client(String clientUUID) {
+        this.client = clientUUID;
+        return this;
+    }
+
     public UserSessionPredicate expired(Integer expired, Integer expiredRefresh) {
         this.expired = expired;
         this.expiredRefresh = expiredRefresh;
@@ -87,6 +94,10 @@ public class UserSessionPredicate implements Predicate<Map.Entry<String, Session
             return false;
         }
 
+        if (client != null && (entity.getAuthenticatedClientSessions() == null || !entity.getAuthenticatedClientSessions().containsKey(client))) {
+            return false;
+        }
+
         if (brokerSessionId != null && !brokerSessionId.equals(entity.getBrokerSessionId())) {
             return false;
         }
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 d87612a..8ab15f7 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
@@ -19,12 +19,12 @@ package org.keycloak.models.sessions.infinispan;
 
 import org.infinispan.Cache;
 import org.keycloak.models.AuthenticatedClientSessionModel;
-import org.keycloak.models.ClientSessionModel;
+import org.keycloak.models.ClientModel;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.UserModel;
 import org.keycloak.models.UserSessionModel;
-import org.keycloak.models.sessions.infinispan.entities.ClientLoginSessionEntity;
+import org.keycloak.models.sessions.infinispan.entities.AuthenticatedClientSessionEntity;
 import org.keycloak.models.sessions.infinispan.entities.SessionEntity;
 import org.keycloak.models.sessions.infinispan.entities.UserSessionEntity;
 
@@ -64,15 +64,31 @@ public class UserSessionAdapter implements UserSessionModel {
 
     @Override
     public Map<String, AuthenticatedClientSessionModel> getAuthenticatedClientSessions() {
-        Map<String, ClientLoginSessionEntity> clientSessionEntities = entity.getClientLoginSessions();
+        Map<String, AuthenticatedClientSessionEntity> clientSessionEntities = entity.getAuthenticatedClientSessions();
         Map<String, AuthenticatedClientSessionModel> result = new HashMap<>();
 
+        List<String> removedClientUUIDS = new LinkedList<>();
+
         if (clientSessionEntities != null) {
-            clientSessionEntities.forEach((String key, ClientLoginSessionEntity value) -> {
-                result.put(key, new AuthenticatedClientSessionAdapter(value, this, provider, cache));
+            clientSessionEntities.forEach((String key, AuthenticatedClientSessionEntity value) -> {
+                // Check if client still exists
+                ClientModel client = realm.getClientById(key);
+                if (client != null) {
+                    result.put(key, new AuthenticatedClientSessionAdapter(value, client, this, provider, cache));
+                } else {
+                    removedClientUUIDS.add(key);
+                }
             });
         }
 
+        // Update user session
+        if (!removedClientUUIDS.isEmpty()) {
+            for (String clientUUID : removedClientUUIDS) {
+                entity.getAuthenticatedClientSessions().remove(clientUUID);
+            }
+            update();
+        }
+
         return Collections.unmodifiableMap(result);
     }
 
@@ -101,6 +117,7 @@ public class UserSessionAdapter implements UserSessionModel {
     @Override
     public void setUser(UserModel user) {
         entity.setUser(user.getId());
+        update();
     }
 
     @Override
@@ -180,19 +197,14 @@ public class UserSessionAdapter implements UserSessionModel {
     }
 
     @Override
-    public List<ClientSessionModel> getClientSessions() {
-        if (entity.getClientSessions() != null) {
-            List<ClientSessionModel> clientSessions = new LinkedList<>();
-            for (String c : entity.getClientSessions()) {
-                ClientSessionModel clientSession = provider.getClientSession(realm, c, offline);
-                if (clientSession != null) {
-                    clientSessions.add(clientSession);
-                }
-            }
-            return clientSessions;
-        } else {
-            return Collections.emptyList();
-        }
+    public void restartSession(RealmModel realm, UserModel user, String loginUsername, String ipAddress, String authMethod, boolean rememberMe, String brokerSessionId, String brokerUserId) {
+        provider.updateSessionEntity(entity, realm, user, loginUsername, ipAddress, authMethod, rememberMe, brokerSessionId, brokerUserId);
+
+        entity.setState(null);
+        entity.setNotes(null);
+        entity.setAuthenticatedClientSessions(null);
+
+        update();
     }
 
     @Override
@@ -217,4 +229,7 @@ public class UserSessionAdapter implements UserSessionModel {
         provider.getTx().replace(cache, entity.getId(), entity);
     }
 
+    Cache<String, SessionEntity> getCache() {
+        return cache;
+    }
 }
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/session/JpaUserSessionPersisterProvider.java b/model/jpa/src/main/java/org/keycloak/models/jpa/session/JpaUserSessionPersisterProvider.java
index 170654f..64246e8 100644
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/session/JpaUserSessionPersisterProvider.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/session/JpaUserSessionPersisterProvider.java
@@ -19,7 +19,6 @@ package org.keycloak.models.jpa.session;
 
 import org.keycloak.models.AuthenticatedClientSessionModel;
 import org.keycloak.models.ClientModel;
-import org.keycloak.models.ClientSessionModel;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.ModelException;
 import org.keycloak.models.RealmModel;
@@ -35,8 +34,9 @@ import javax.persistence.EntityManager;
 import javax.persistence.Query;
 import javax.persistence.TypedQuery;
 import java.util.ArrayList;
-import java.util.LinkedList;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 
 /**
  * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
@@ -69,12 +69,11 @@ public class JpaUserSessionPersisterProvider implements UserSessionPersisterProv
     }
 
     @Override
-    public void createClientSession(UserSessionModel userSession, AuthenticatedClientSessionModel clientSession, boolean offline) {
+    public void createClientSession(AuthenticatedClientSessionModel clientSession, boolean offline) {
         PersistentAuthenticatedClientSessionAdapter adapter = new PersistentAuthenticatedClientSessionAdapter(clientSession);
         PersistentClientSessionModel model = adapter.getUpdatedModel();
 
         PersistentClientSessionEntity entity = new PersistentClientSessionEntity();
-        entity.setClientSessionId(clientSession.getId());
         entity.setClientId(clientSession.getClient().getId());
         entity.setTimestamp(clientSession.getTimestamp());
         String offlineStr = offlineToString(offline);
@@ -122,9 +121,9 @@ public class JpaUserSessionPersisterProvider implements UserSessionPersisterProv
     }
 
     @Override
-    public void removeClientSession(String clientSessionId, boolean offline) {
+    public void removeClientSession(String userSessionId, String clientUUID, boolean offline) {
         String offlineStr = offlineToString(offline);
-        PersistentClientSessionEntity sessionEntity = em.find(PersistentClientSessionEntity.class, new PersistentClientSessionEntity.Key(clientSessionId, offlineStr));
+        PersistentClientSessionEntity sessionEntity = em.find(PersistentClientSessionEntity.class, new PersistentClientSessionEntity.Key(userSessionId, clientUUID, offlineStr));
         if (sessionEntity != null) {
             em.remove(sessionEntity);
 
@@ -218,8 +217,6 @@ public class JpaUserSessionPersisterProvider implements UserSessionPersisterProv
             userSessionIds.add(entity.getUserSessionId());
         }
 
-        // TODO:mposolda
-        /*
         if (!userSessionIds.isEmpty()) {
             TypedQuery<PersistentClientSessionEntity> query2 = em.createNamedQuery("findClientSessionsByUserSessions", PersistentClientSessionEntity.class);
             query2.setParameter("userSessionIds", userSessionIds);
@@ -230,14 +227,14 @@ public class JpaUserSessionPersisterProvider implements UserSessionPersisterProv
             int j = 0;
             for (UserSessionModel ss : result) {
                 PersistentUserSessionAdapter userSession = (PersistentUserSessionAdapter) ss;
-                List<ClientSessionModel> currentClientSessions = userSession.getClientSessions(); // This is empty now and we want to fill it
+                Map<String, AuthenticatedClientSessionModel> currentClientSessions = userSession.getAuthenticatedClientSessions(); // This is empty now and we want to fill it
 
                 boolean next = true;
                 while (next && j < clientSessions.size()) {
                     PersistentClientSessionEntity clientSession = clientSessions.get(j);
                     if (clientSession.getUserSessionId().equals(userSession.getId())) {
-                        PersistentClientSessionAdapter clientSessAdapter = toAdapter(userSession.getRealm(), userSession, clientSession);
-                        currentClientSessions.add(clientSessAdapter);
+                        PersistentAuthenticatedClientSessionAdapter clientSessAdapter = toAdapter(userSession.getRealm(), userSession, clientSession);
+                        currentClientSessions.put(clientSession.getClientId(), clientSessAdapter);
                         j++;
                     } else {
                         next = false;
@@ -245,7 +242,7 @@ public class JpaUserSessionPersisterProvider implements UserSessionPersisterProv
                 }
             }
         }
-        */
+
 
         return result;
     }
@@ -256,7 +253,7 @@ public class JpaUserSessionPersisterProvider implements UserSessionPersisterProv
         model.setLastSessionRefresh(entity.getLastSessionRefresh());
         model.setData(entity.getData());
 
-        List<ClientSessionModel> clientSessions = new LinkedList<>();
+        Map<String, AuthenticatedClientSessionModel> clientSessions = new HashMap<>();
         return new PersistentUserSessionAdapter(model, realm, user, clientSessions);
     }
 
@@ -264,7 +261,6 @@ public class JpaUserSessionPersisterProvider implements UserSessionPersisterProv
         ClientModel client = realm.getClientById(entity.getClientId());
 
         PersistentClientSessionModel model = new PersistentClientSessionModel();
-        model.setClientSessionId(entity.getClientSessionId());
         model.setClientId(entity.getClientId());
         model.setUserSessionId(userSession.getId());
         model.setUserId(userSession.getUser().getId());
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/session/JpaUserSessionPersisterProviderFactory.java b/model/jpa/src/main/java/org/keycloak/models/jpa/session/JpaUserSessionPersisterProviderFactory.java
index 35265af..e12223d 100644
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/session/JpaUserSessionPersisterProviderFactory.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/session/JpaUserSessionPersisterProviderFactory.java
@@ -20,7 +20,6 @@ package org.keycloak.models.jpa.session;
 import org.keycloak.Config;
 import org.keycloak.connections.jpa.JpaConnectionProvider;
 import org.keycloak.models.KeycloakSession;
-import org.keycloak.models.KeycloakSessionFactory;
 import org.keycloak.models.session.UserSessionPersisterProvider;
 import org.keycloak.models.session.UserSessionPersisterProviderFactory;
 
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/session/PersistentClientSessionEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/session/PersistentClientSessionEntity.java
index 7250836..8910bca 100644
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/session/PersistentClientSessionEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/session/PersistentClientSessionEntity.java
@@ -45,12 +45,10 @@ import java.io.Serializable;
 public class PersistentClientSessionEntity {
 
     @Id
-    @Column(name="CLIENT_SESSION_ID", length = 36)
-    protected String clientSessionId;
-
     @Column(name = "USER_SESSION_ID", length = 36)
     protected String userSessionId;
 
+    @Id
     @Column(name="CLIENT_ID", length = 36)
     protected String clientId;
 
@@ -64,14 +62,6 @@ public class PersistentClientSessionEntity {
     @Column(name="DATA")
     protected String data;
 
-    public String getClientSessionId() {
-        return clientSessionId;
-    }
-
-    public void setClientSessionId(String clientSessionId) {
-        this.clientSessionId = clientSessionId;
-    }
-
     public String getUserSessionId() {
         return userSessionId;
     }
@@ -114,20 +104,27 @@ public class PersistentClientSessionEntity {
 
     public static class Key implements Serializable {
 
-        protected String clientSessionId;
+        protected String userSessionId;
+
+        protected String clientId;
 
         protected String offline;
 
         public Key() {
         }
 
-        public Key(String clientSessionId, String offline) {
-            this.clientSessionId = clientSessionId;
+        public Key(String userSessionId, String clientId, String offline) {
+            this.userSessionId = userSessionId;
+            this.clientId = clientId;
             this.offline = offline;
         }
 
-        public String getClientSessionId() {
-            return clientSessionId;
+        public String getUserSessionId() {
+            return userSessionId;
+        }
+
+        public String getClientId() {
+            return clientId;
         }
 
         public String getOffline() {
@@ -141,7 +138,8 @@ public class PersistentClientSessionEntity {
 
             Key key = (Key) o;
 
-            if (this.clientSessionId != null ? !this.clientSessionId.equals(key.clientSessionId) : key.clientSessionId != null) return false;
+            if (this.userSessionId != null ? !this.userSessionId.equals(key.userSessionId) : key.userSessionId != null) return false;
+            if (this.clientId != null ? !this.clientId.equals(key.clientId) : key.clientId != null) return false;
             if (this.offline != null ? !this.offline.equals(key.offline) : key.offline != null) return false;
 
             return true;
@@ -149,7 +147,8 @@ public class PersistentClientSessionEntity {
 
         @Override
         public int hashCode() {
-            int result = this.clientSessionId != null ? this.clientSessionId.hashCode() : 0;
+            int result = this.userSessionId != null ? this.userSessionId.hashCode() : 0;
+            result = 37 * result + (this.clientId != null ? this.clientId.hashCode() : 0);
             result = 31 * result + (this.offline != null ? this.offline.hashCode() : 0);
             return result;
         }
diff --git a/model/jpa/src/main/resources/META-INF/jpa-changelog-3.2.0.xml b/model/jpa/src/main/resources/META-INF/jpa-changelog-3.2.0.xml
new file mode 100644
index 0000000..c453a2e
--- /dev/null
+++ b/model/jpa/src/main/resources/META-INF/jpa-changelog-3.2.0.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!--
+  ~ 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.
+  -->
+
+<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.1.xsd">
+
+    <changeSet author="mposolda@redhat.com" id="3.2.0">
+        <dropPrimaryKey constraintName="CONSTRAINT_OFFLINE_CL_SES_PK2" tableName="OFFLINE_CLIENT_SESSION" />
+        <dropColumn tableName="OFFLINE_CLIENT_SESSION" columnName="CLIENT_SESSION_ID" />
+        <addPrimaryKey columnNames="USER_SESSION_ID,CLIENT_ID, OFFLINE_FLAG" constraintName="CONSTRAINT_OFFL_CL_SES_PK3" tableName="OFFLINE_CLIENT_SESSION"/>
+    </changeSet>
+
+</databaseChangeLog>
\ No newline at end of file
diff --git a/model/jpa/src/main/resources/META-INF/jpa-changelog-master.xml b/model/jpa/src/main/resources/META-INF/jpa-changelog-master.xml
index 59855ec..ae7d98b 100755
--- a/model/jpa/src/main/resources/META-INF/jpa-changelog-master.xml
+++ b/model/jpa/src/main/resources/META-INF/jpa-changelog-master.xml
@@ -47,4 +47,5 @@
     <include file="META-INF/jpa-changelog-2.5.0.xml"/>
     <include file="META-INF/jpa-changelog-2.5.1.xml"/>
     <include file="META-INF/jpa-changelog-3.0.0.xml"/>
+    <include file="META-INF/jpa-changelog-3.2.0.xml"/>
 </databaseChangeLog>
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 15dc57f..099a39c 100644
--- a/server-spi/src/main/java/org/keycloak/models/AuthenticatedClientSessionModel.java
+++ b/server-spi/src/main/java/org/keycloak/models/AuthenticatedClientSessionModel.java
@@ -30,8 +30,8 @@ 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();
+    String getNote(String name);
+    void setNote(String name, String value);
+    void removeNote(String name);
+    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 0dc2f5c..28a3145 100755
--- a/server-spi/src/main/java/org/keycloak/models/UserSessionModel.java
+++ b/server-spi/src/main/java/org/keycloak/models/UserSessionModel.java
@@ -17,7 +17,6 @@
 
 package org.keycloak.models;
 
-import java.util.List;
 import java.util.Map;
 
 /**
@@ -55,9 +54,6 @@ public interface UserSessionModel {
 
     Map<String, AuthenticatedClientSessionModel> getAuthenticatedClientSessions();
 
-    // TODO: Remove
-    List<ClientSessionModel> getClientSessions();
-
     public String getNote(String name);
     public void setNote(String name, String value);
     public void removeNote(String name);
@@ -68,8 +64,10 @@ public interface UserSessionModel {
 
     void setUser(UserModel user);
 
+    // Will completely restart whole state of user session. It will just keep same ID.
+    void restartSession(RealmModel realm, UserModel user, String loginUsername, String ipAddress, String authMethod, boolean rememberMe, String brokerSessionId, String brokerUserId);
+
     public static enum State {
-        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 1afbcba..d474e89 100755
--- a/server-spi/src/main/java/org/keycloak/models/UserSessionProvider.java
+++ b/server-spi/src/main/java/org/keycloak/models/UserSessionProvider.java
@@ -27,10 +27,7 @@ import java.util.List;
  */
 public interface UserSessionProvider extends Provider {
 
-    ClientSessionModel createClientSession(RealmModel realm, ClientModel client);
     AuthenticatedClientSessionModel createClientSession(RealmModel realm, ClientModel client, UserSessionModel userSession);
-    ClientSessionModel getClientSession(RealmModel realm, String id);
-    ClientSessionModel getClientSession(String id);
 
     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);
@@ -42,14 +39,13 @@ public interface UserSessionProvider extends Provider {
 
     long getActiveUserSessions(RealmModel realm, ClientModel client);
 
-    // This will remove attached ClientLoginSessionModels too
+    /** This will remove attached ClientLoginSessionModels too **/
     void removeUserSession(RealmModel realm, UserSessionModel session);
     void removeUserSessions(RealmModel realm, UserModel user);
 
-    // Implementation should propagate removal of expired userSessions to userSessionPersister too
+    /** Implementation should propagate removal of expired userSessions to userSessionPersister too **/
     void removeExpired(RealmModel realm);
     void removeUserSessions(RealmModel realm);
-    void removeClientSession(RealmModel realm, ClientSessionModel clientSession);
 
     UserLoginFailureModel getUserLoginFailure(RealmModel realm, String userId);
     UserLoginFailureModel addUserLoginFailure(RealmModel realm, String userId);
@@ -59,25 +55,22 @@ public interface UserSessionProvider extends Provider {
     void onRealmRemoved(RealmModel realm);
     void onClientRemoved(RealmModel realm, ClientModel client);
 
+    /** Newly created userSession won't contain attached AuthenticatedClientSessions **/
     UserSessionModel createOfflineUserSession(UserSessionModel userSession);
     UserSessionModel getOfflineUserSession(RealmModel realm, String userSessionId);
 
-    // Removes the attached clientSessions as well
+    /** Removes the attached clientSessions as well **/
     void removeOfflineUserSession(RealmModel realm, UserSessionModel userSession);
 
-    AuthenticatedClientSessionModel createOfflineClientSession(AuthenticatedClientSessionModel clientSession);
-    ClientSessionModel getOfflineClientSession(RealmModel realm, String clientSessionId);
+    /** Will automatically attach newly created offline client session to the offlineUserSession **/
+    AuthenticatedClientSessionModel createOfflineClientSession(AuthenticatedClientSessionModel clientSession, UserSessionModel offlineUserSession);
     List<UserSessionModel> getOfflineUserSessions(RealmModel realm, UserModel user);
 
-    // Don't remove userSession even if it's last userSession
-    void removeOfflineClientSession(RealmModel realm, String clientSessionId);
-
     long getOfflineSessionsCount(RealmModel realm, ClientModel client);
     List<UserSessionModel> getOfflineUserSessions(RealmModel realm, ClientModel client, int first, int max);
 
-    // Triggered by persister during pre-load
-    UserSessionModel importUserSession(UserSessionModel persistentUserSession, boolean offline);
-    ClientSessionModel importClientSession(ClientSessionModel persistentClientSession, boolean offline);
+    /** Triggered by persister during pre-load. It optionally imports authenticatedClientSessions too if requested. Otherwise the imported UserSession will have empty list of AuthenticationSessionModel **/
+    UserSessionModel importUserSession(UserSessionModel persistentUserSession, boolean offline, boolean importAuthenticatedClientSessions);
 
     ClientInitialAccessModel createClientInitialAccessModel(RealmModel realm, int expiration, int count);
     ClientInitialAccessModel getClientInitialAccessModel(RealmModel realm, String id);
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 42884cc..f284d93 100644
--- a/server-spi/src/main/java/org/keycloak/sessions/AuthenticationSessionProvider.java
+++ b/server-spi/src/main/java/org/keycloak/sessions/AuthenticationSessionProvider.java
@@ -35,7 +35,6 @@ public interface AuthenticationSessionProvider extends Provider {
 
     void removeAuthenticationSession(RealmModel realm, AuthenticationSessionModel authenticationSession);
 
-    // TODO: test and add to scheduler
     void removeExpired(RealmModel realm);
     void onRealmRemoved(RealmModel realm);
     void onClientRemoved(RealmModel realm, ClientModel client);
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 9d71b78..0320299 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
@@ -17,7 +17,6 @@
 package org.keycloak.broker.provider;
 
 import org.keycloak.events.EventBuilder;
-import org.keycloak.models.ClientSessionModel;
 import org.keycloak.models.IdentityProviderModel;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.RealmModel;
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 7affd99..ba8276f 100644
--- a/server-spi-private/src/main/java/org/keycloak/broker/provider/AuthenticationRequest.java
+++ b/server-spi-private/src/main/java/org/keycloak/broker/provider/AuthenticationRequest.java
@@ -17,7 +17,6 @@
 package org.keycloak.broker.provider;
 
 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;
diff --git a/server-spi-private/src/main/java/org/keycloak/forms/login/LoginFormsProvider.java b/server-spi-private/src/main/java/org/keycloak/forms/login/LoginFormsProvider.java
index f94e588..32195ef 100755
--- a/server-spi-private/src/main/java/org/keycloak/forms/login/LoginFormsProvider.java
+++ b/server-spi-private/src/main/java/org/keycloak/forms/login/LoginFormsProvider.java
@@ -78,8 +78,6 @@ public interface LoginFormsProvider extends Provider {
 
     public LoginFormsProvider setClientSessionCode(String accessCode);
 
-    public LoginFormsProvider setAuthenticationSession(AuthenticationSessionModel authenticationSession);
-
     public LoginFormsProvider setAccessRequest(List<RoleModel> realmRolesRequested, MultivaluedMap<String,RoleModel> resourceRolesRequested, List<ProtocolMapperModel> protocolMappers);
     public LoginFormsProvider setAccessRequest(String message);
 
diff --git a/server-spi-private/src/main/java/org/keycloak/models/session/DisabledUserSessionPersisterProvider.java b/server-spi-private/src/main/java/org/keycloak/models/session/DisabledUserSessionPersisterProvider.java
index 51efc23..10b28a2 100644
--- a/server-spi-private/src/main/java/org/keycloak/models/session/DisabledUserSessionPersisterProvider.java
+++ b/server-spi-private/src/main/java/org/keycloak/models/session/DisabledUserSessionPersisterProvider.java
@@ -70,7 +70,7 @@ public class DisabledUserSessionPersisterProvider implements UserSessionPersiste
     }
 
     @Override
-    public void createClientSession(UserSessionModel userSession, AuthenticatedClientSessionModel clientSession, boolean offline) {
+    public void createClientSession(AuthenticatedClientSessionModel clientSession, boolean offline) {
 
     }
 
@@ -85,7 +85,7 @@ public class DisabledUserSessionPersisterProvider implements UserSessionPersiste
     }
 
     @Override
-    public void removeClientSession(String clientSessionId, boolean offline) {
+    public void removeClientSession(String userSessionId, String clientUUID, boolean offline) {
 
     }
 
diff --git a/server-spi-private/src/main/java/org/keycloak/models/session/PersistentAuthenticatedClientSessionAdapter.java b/server-spi-private/src/main/java/org/keycloak/models/session/PersistentAuthenticatedClientSessionAdapter.java
index d410aba..20c3cb6 100644
--- a/server-spi-private/src/main/java/org/keycloak/models/session/PersistentAuthenticatedClientSessionAdapter.java
+++ b/server-spi-private/src/main/java/org/keycloak/models/session/PersistentAuthenticatedClientSessionAdapter.java
@@ -20,7 +20,6 @@ package org.keycloak.models.session;
 import com.fasterxml.jackson.annotation.JsonProperty;
 import org.keycloak.models.AuthenticatedClientSessionModel;
 import org.keycloak.models.ClientModel;
-import org.keycloak.models.ClientSessionModel;
 import org.keycloak.models.ModelException;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.UserSessionModel;
@@ -55,10 +54,7 @@ public class PersistentAuthenticatedClientSessionAdapter implements Authenticate
 
         model = new PersistentClientSessionModel();
         model.setClientId(clientSession.getClient().getId());
-        model.setClientSessionId(clientSession.getId());
-        if (clientSession.getUserSession() != null) {
-            model.setUserId(clientSession.getUserSession().getUser().getId());
-        }
+        model.setUserId(clientSession.getUserSession().getUser().getId());
         model.setUserSessionId(clientSession.getUserSession().getId());
         model.setTimestamp(clientSession.getTimestamp());
 
@@ -101,7 +97,7 @@ public class PersistentAuthenticatedClientSessionAdapter implements Authenticate
 
     @Override
     public String getId() {
-        return model.getClientSessionId();
+        return null;
     }
 
     @Override
@@ -194,7 +190,7 @@ public class PersistentAuthenticatedClientSessionAdapter implements Authenticate
     public void setNote(String name, String value) {
         PersistentClientSessionData entity = getData();
         if (entity.getNotes() == null) {
-            entity.setNotes(new HashMap<String, String>());
+            entity.setNotes(new HashMap<>());
         }
         entity.getNotes().put(name, value);
     }
@@ -214,13 +210,12 @@ public class PersistentAuthenticatedClientSessionAdapter implements Authenticate
         return entity.getNotes();
     }
 
-
     @Override
     public boolean equals(Object o) {
         if (this == o) return true;
-        if (o == null || !(o instanceof ClientSessionModel)) return false;
+        if (o == null || !(o instanceof AuthenticatedClientSessionModel)) return false;
 
-        ClientSessionModel that = (ClientSessionModel) o;
+        AuthenticatedClientSessionModel that = (AuthenticatedClientSessionModel) o;
         return that.getId().equals(getId());
     }
 
diff --git a/server-spi-private/src/main/java/org/keycloak/models/session/PersistentClientSessionModel.java b/server-spi-private/src/main/java/org/keycloak/models/session/PersistentClientSessionModel.java
index 5990eea..ee33fed 100644
--- a/server-spi-private/src/main/java/org/keycloak/models/session/PersistentClientSessionModel.java
+++ b/server-spi-private/src/main/java/org/keycloak/models/session/PersistentClientSessionModel.java
@@ -22,20 +22,12 @@ package org.keycloak.models.session;
  */
 public class PersistentClientSessionModel {
 
-    private String clientSessionId;
     private String userSessionId;
     private String clientId;
     private String userId;
     private int timestamp;
     private String data;
 
-    public String getClientSessionId() {
-        return clientSessionId;
-    }
-
-    public void setClientSessionId(String clientSessionId) {
-        this.clientSessionId = clientSessionId;
-    }
 
     public String getUserSessionId() {
         return userSessionId;
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 7ba4bc4..170d381 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
@@ -19,7 +19,6 @@ package org.keycloak.models.session;
 
 import com.fasterxml.jackson.annotation.JsonProperty;
 import org.keycloak.models.AuthenticatedClientSessionModel;
-import org.keycloak.models.ClientSessionModel;
 import org.keycloak.models.ModelException;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.UserModel;
@@ -28,7 +27,6 @@ import org.keycloak.util.JsonSerialization;
 
 import java.io.IOException;
 import java.util.HashMap;
-import java.util.List;
 import java.util.Map;
 
 /**
@@ -39,7 +37,7 @@ public class PersistentUserSessionAdapter implements UserSessionModel {
     private final PersistentUserSessionModel model;
     private final UserModel user;
     private final RealmModel realm;
-    private final List<ClientSessionModel> clientSessions;
+    private final Map<String, AuthenticatedClientSessionModel> authenticatedClientSessions;
 
     private PersistentUserSessionData data;
 
@@ -60,14 +58,14 @@ public class PersistentUserSessionAdapter implements UserSessionModel {
 
         this.user = other.getUser();
         this.realm = other.getRealm();
-        this.clientSessions = other.getClientSessions();
+        this.authenticatedClientSessions = other.getAuthenticatedClientSessions();
     }
 
-    public PersistentUserSessionAdapter(PersistentUserSessionModel model, RealmModel realm, UserModel user, List<ClientSessionModel> clientSessions) {
+    public PersistentUserSessionAdapter(PersistentUserSessionModel model, RealmModel realm, UserModel user, Map<String, AuthenticatedClientSessionModel> clientSessions) {
         this.model = model;
         this.realm = realm;
         this.user = user;
-        this.clientSessions = clientSessions;
+        this.authenticatedClientSessions = clientSessions;
     }
 
     // Lazily init data
@@ -161,14 +159,8 @@ public class PersistentUserSessionAdapter implements UserSessionModel {
     }
 
     @Override
-    public List<ClientSessionModel> getClientSessions() {
-        return clientSessions;
-    }
-
-    // TODO:mposolda
-    @Override
     public Map<String, AuthenticatedClientSessionModel> getAuthenticatedClientSessions() {
-        return null;
+        return authenticatedClientSessions;
     }
 
     @Override
@@ -209,6 +201,11 @@ public class PersistentUserSessionAdapter implements UserSessionModel {
     }
 
     @Override
+    public void restartSession(RealmModel realm, UserModel user, String loginUsername, String ipAddress, String authMethod, boolean rememberMe, String brokerSessionId, String brokerUserId) {
+        throw new IllegalStateException("Not supported");
+    }
+
+    @Override
     public boolean equals(Object o) {
         if (this == o) return true;
         if (o == null || !(o instanceof UserSessionModel)) return false;
diff --git a/server-spi-private/src/main/java/org/keycloak/models/session/UserSessionPersisterProvider.java b/server-spi-private/src/main/java/org/keycloak/models/session/UserSessionPersisterProvider.java
index c5370ed..ba5a595 100644
--- a/server-spi-private/src/main/java/org/keycloak/models/session/UserSessionPersisterProvider.java
+++ b/server-spi-private/src/main/java/org/keycloak/models/session/UserSessionPersisterProvider.java
@@ -35,7 +35,7 @@ public interface UserSessionPersisterProvider extends Provider {
     void createUserSession(UserSessionModel userSession, boolean offline);
 
     // Assuming that corresponding userSession is already persisted
-    void createClientSession(UserSessionModel userSession, AuthenticatedClientSessionModel clientSession, boolean offline);
+    void createClientSession(AuthenticatedClientSessionModel clientSession, boolean offline);
 
     void updateUserSession(UserSessionModel userSession, boolean offline);
 
@@ -43,7 +43,7 @@ public interface UserSessionPersisterProvider extends Provider {
     void removeUserSession(String userSessionId, boolean offline);
 
     // Called during revoke. It will remove userSession too if this was last clientSession attached to it
-    void removeClientSession(String clientSessionId, boolean offline);
+    void removeClientSession(String userSessionId, String clientUUID, boolean offline);
 
     void onRealmRemoved(RealmModel realm);
     void onClientRemoved(RealmModel realm, ClientModel client);
diff --git a/services/src/main/java/org/keycloak/authentication/actiontoken/resetcred/ResetCredentialsActionTokenHandler.java b/services/src/main/java/org/keycloak/authentication/actiontoken/resetcred/ResetCredentialsActionTokenHandler.java
index fd87a6e..c6f834b 100644
--- a/services/src/main/java/org/keycloak/authentication/actiontoken/resetcred/ResetCredentialsActionTokenHandler.java
+++ b/services/src/main/java/org/keycloak/authentication/actiontoken/resetcred/ResetCredentialsActionTokenHandler.java
@@ -20,12 +20,14 @@ import org.keycloak.TokenVerifier.Predicate;
 import org.keycloak.authentication.AuthenticationProcessor;
 import org.keycloak.authentication.actiontoken.*;
 import org.keycloak.authentication.authenticators.broker.AbstractIdpAuthenticator;
+import org.keycloak.authentication.authenticators.broker.util.SerializedBrokeredIdentityContext;
 import org.keycloak.events.Errors;
 import org.keycloak.events.EventType;
 import org.keycloak.models.UserModel;
 import org.keycloak.services.ErrorPage;
 import org.keycloak.services.managers.AuthenticationSessionManager;
 import org.keycloak.services.messages.Messages;
+import org.keycloak.services.resources.LoginActionsService;
 import org.keycloak.services.resources.LoginActionsServiceChecks.IsActionRequired;
 import org.keycloak.sessions.CommonClientSessionModel.Action;
 import javax.ws.rs.core.Response;
@@ -61,7 +63,7 @@ public class ResetCredentialsActionTokenHandler extends AbstractActionTokenHande
 
     @Override
     public Response handleToken(ResetCredentialsActionToken token, ActionTokenContext tokenContext, ProcessFlow processFlow) {
-        AuthenticationProcessor authProcessor = new ResetCredsAuthenticationProcessor(tokenContext);
+        AuthenticationProcessor authProcessor = new ResetCredsAuthenticationProcessor();
 
         return processFlow.processFlow(
           false,
@@ -87,34 +89,31 @@ public class ResetCredentialsActionTokenHandler extends AbstractActionTokenHande
 
     public static class ResetCredsAuthenticationProcessor extends AuthenticationProcessor {
 
-        private final ActionTokenContext tokenContext;
-
-        public ResetCredsAuthenticationProcessor(ActionTokenContext tokenContext) {
-            this.tokenContext = tokenContext;
-        }
-
         @Override
         protected Response authenticationComplete() {
-            boolean firstBrokerLoginInProgress = (tokenContext.getAuthenticationSession().getAuthNote(AbstractIdpAuthenticator.BROKERED_CONTEXT_NOTE) != null);
+            boolean firstBrokerLoginInProgress = (authenticationSession.getAuthNote(AbstractIdpAuthenticator.BROKERED_CONTEXT_NOTE) != null);
             if (firstBrokerLoginInProgress) {
 
-                UserModel linkingUser = AbstractIdpAuthenticator.getExistingUser(session, tokenContext.getRealm(), tokenContext.getAuthenticationSession());
-                if (!linkingUser.getId().equals(tokenContext.getAuthenticationSession().getAuthenticatedUser().getId())) {
+                UserModel linkingUser = AbstractIdpAuthenticator.getExistingUser(session, realm, authenticationSession);
+                if (!linkingUser.getId().equals(authenticationSession.getAuthenticatedUser().getId())) {
                     return ErrorPage.error(session,
                       Messages.IDENTITY_PROVIDER_DIFFERENT_USER_MESSAGE,
-                      tokenContext.getAuthenticationSession().getAuthenticatedUser().getUsername(),
+                      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(authenticationSession, AbstractIdpAuthenticator.BROKERED_CONTEXT_NOTE);
+                authenticationSession.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 LoginActionsService.redirectToAfterBrokerLoginEndpoint(session, realm, uriInfo, authenticationSession, true);
             } else {
                 return super.authenticationComplete();
             }
         }
+
     }
 }
diff --git a/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java b/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java
index 0daec9a..2427091 100755
--- a/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java
+++ b/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java
@@ -33,7 +33,6 @@ import org.keycloak.models.AuthenticationFlowModel;
 import org.keycloak.models.AuthenticatorConfigModel;
 import org.keycloak.models.AuthenticatedClientSessionModel;
 import org.keycloak.models.ClientModel;
-import org.keycloak.models.ClientSessionModel;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.UserModel;
@@ -53,11 +52,11 @@ 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.services.util.AuthenticationFlowURLHelper;
 import org.keycloak.sessions.AuthenticationSessionModel;
+import org.keycloak.sessions.CommonClientSessionModel;
 
 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;
@@ -73,7 +72,6 @@ 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";
@@ -595,9 +593,9 @@ public class AuthenticationProcessor {
     }
 
     public boolean isSuccessful(AuthenticationExecutionModel model) {
-        ClientSessionModel.ExecutionStatus status = authenticationSession.getExecutionStatus().get(model.getId());
+        AuthenticationSessionModel.ExecutionStatus status = authenticationSession.getExecutionStatus().get(model.getId());
         if (status == null) return false;
-        return status == ClientSessionModel.ExecutionStatus.SUCCESS;
+        return status == AuthenticationSessionModel.ExecutionStatus.SUCCESS;
     }
 
     public Response handleBrowserException(Exception failure) {
@@ -629,7 +627,7 @@ public class AuthenticationProcessor {
             } else if (e.getError() == AuthenticationFlowError.FORK_FLOW) {
                 ForkFlowException reset = (ForkFlowException)e;
                 AuthenticationSessionModel clone = clone(session, authenticationSession);
-                clone.setAction(ClientSessionModel.Action.AUTHENTICATE.name());
+                clone.setAction(AuthenticationSessionModel.Action.AUTHENTICATE.name());
                 setAuthenticationSession(clone);
 
                 AuthenticationProcessor processor = new AuthenticationProcessor();
@@ -726,9 +724,9 @@ public class AuthenticationProcessor {
 
 
     public Response redirectToFlow() {
-        URI redirect = new PageExpiredRedirect(session, realm, uriInfo).getLastExecutionUrl(authenticationSession);
+        URI redirect = new AuthenticationFlowURLHelper(session, realm, uriInfo).getLastExecutionUrl(authenticationSession);
 
-        logger.info("Redirecting to URL: " + redirect.toString());
+        logger.debug("Redirecting to URL: " + redirect.toString());
 
         return Response.status(302).location(redirect).build();
 
@@ -750,6 +748,8 @@ public class AuthenticationProcessor {
         authSession.clearUserSessionNotes();
         authSession.clearAuthNotes();
 
+        authSession.setAction(CommonClientSessionModel.Action.AUTHENTICATE.name());
+
         authSession.setAuthNote(CURRENT_FLOW_PATH, flowPath);
     }
 
@@ -766,7 +766,7 @@ public class AuthenticationProcessor {
         clone.setTimestamp(Time.currentTime());
 
         clone.setAuthNote(FORKED_FROM, authSession.getId());
-        logger.infof("Forked authSession %s from authSession %s", clone.getId(), authSession.getId());
+        logger.debugf("Forked authSession %s from authSession %s", clone.getId(), authSession.getId());
 
         return clone;
 
@@ -777,10 +777,9 @@ public class AuthenticationProcessor {
         logger.debug("authenticationAction");
         checkClientSession(true);
         String current = authenticationSession.getAuthNote(CURRENT_AUTHENTICATION_EXECUTION);
-        if (!execution.equals(current)) {
-            // TODO:mposolda debug
-            logger.info("Current execution does not equal executed execution.  Might be a page refresh");
-            return new PageExpiredRedirect(session, realm, uriInfo).showPageExpired(authenticationSession);
+        if (execution == null || !execution.equals(current)) {
+            logger.debug("Current execution does not equal executed execution.  Might be a page refresh");
+            return new AuthenticationFlowURLHelper(session, realm, uriInfo).showPageExpired(authenticationSession);
         }
         UserModel authUser = authenticationSession.getAuthenticatedUser();
         validateUser(authUser);
@@ -812,7 +811,7 @@ public class AuthenticationProcessor {
         ClientSessionCode code = new ClientSessionCode(session, realm, authenticationSession);
 
         if (checkAction) {
-            String action = ClientSessionModel.Action.AUTHENTICATE.name();
+            String action = AuthenticationSessionModel.Action.AUTHENTICATE.name();
             if (!code.isValidAction(action)) {
                 throw new AuthenticationFlowException(AuthenticationFlowError.INVALID_CLIENT_SESSION);
             }
@@ -862,26 +861,29 @@ public class AuthenticationProcessor {
         if (attemptedUsername != null) username = attemptedUsername;
         String rememberMe = authSession.getAuthNote(Details.REMEMBER_ME);
         boolean remember = rememberMe != null && rememberMe.equalsIgnoreCase("true");
+        String brokerSessionId = authSession.getAuthNote(BROKER_SESSION_ID);
+        String brokerUserId = authSession.getAuthNote(BROKER_USER_ID);
 
         if (userSession == null) { // if no authenticator attached a usersession
 
             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 if (userSession.getUser() == null || !AuthenticationManager.isSessionValid(realm, userSession)) {
+                userSession.restartSession(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.");
+                // We need to handle case when different user was used
+                logger.debugf("No SSO login, but found existing userSession with ID '%s' after finished authentication.", userSession.getId());
                 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);
+            userSession.setState(UserSessionModel.State.LOGGED_IN);
         }
 
         if (remember) {
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/browser/ScriptBasedAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/browser/ScriptBasedAuthenticator.java
index 5e0851a..af63974 100644
--- a/services/src/main/java/org/keycloak/authentication/authenticators/browser/ScriptBasedAuthenticator.java
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/browser/ScriptBasedAuthenticator.java
@@ -47,7 +47,7 @@ import java.util.Map;
  * <li>{@code realm} the {@link RealmModel}</li>
  * <li>{@code user} the current {@link UserModel}</li>
  * <li>{@code session} the active {@link KeycloakSession}</li>
- * <li>{@code clientSession} the current {@link org.keycloak.models.ClientSessionModel}</li>
+ * <li>{@code clientSession} the current {@link org.keycloak.sessions.AuthenticationSessionModel}</li>
  * <li>{@code httpRequest} the current {@link org.jboss.resteasy.spi.HttpRequest}</li>
  * <li>{@code LOG} a {@link org.jboss.logging.Logger} scoped to {@link ScriptBasedAuthenticator}/li>
  * </ol>
diff --git a/services/src/main/java/org/keycloak/authentication/DefaultAuthenticationFlow.java b/services/src/main/java/org/keycloak/authentication/DefaultAuthenticationFlow.java
index dfa1afa..3a9c53c 100755
--- a/services/src/main/java/org/keycloak/authentication/DefaultAuthenticationFlow.java
+++ b/services/src/main/java/org/keycloak/authentication/DefaultAuthenticationFlow.java
@@ -20,9 +20,9 @@ package org.keycloak.authentication;
 import org.jboss.logging.Logger;
 import org.keycloak.models.AuthenticationExecutionModel;
 import org.keycloak.models.AuthenticationFlowModel;
-import org.keycloak.models.ClientSessionModel;
 import org.keycloak.models.UserModel;
 import org.keycloak.services.ServicesLogger;
+import org.keycloak.sessions.AuthenticationSessionModel;
 
 import javax.ws.rs.core.Response;
 import java.util.Iterator;
@@ -51,11 +51,11 @@ public class DefaultAuthenticationFlow implements AuthenticationFlow {
 
     protected boolean isProcessed(AuthenticationExecutionModel model) {
         if (model.isDisabled()) return true;
-        ClientSessionModel.ExecutionStatus status = processor.getAuthenticationSession().getExecutionStatus().get(model.getId());
+        AuthenticationSessionModel.ExecutionStatus status = processor.getAuthenticationSession().getExecutionStatus().get(model.getId());
         if (status == null) return false;
-        return status == ClientSessionModel.ExecutionStatus.SUCCESS || status == ClientSessionModel.ExecutionStatus.SKIPPED
-                || status == ClientSessionModel.ExecutionStatus.ATTEMPTED
-                || status == ClientSessionModel.ExecutionStatus.SETUP_REQUIRED;
+        return status == AuthenticationSessionModel.ExecutionStatus.SUCCESS || status == AuthenticationSessionModel.ExecutionStatus.SKIPPED
+                || status == AuthenticationSessionModel.ExecutionStatus.ATTEMPTED
+                || status == AuthenticationSessionModel.ExecutionStatus.SETUP_REQUIRED;
     }
 
 
@@ -75,7 +75,7 @@ public class DefaultAuthenticationFlow implements AuthenticationFlow {
                 AuthenticationFlow authenticationFlow = processor.createFlowExecution(model.getFlowId(), model);
                 Response flowChallenge = authenticationFlow.processAction(actionExecution);
                 if (flowChallenge == null) {
-                    processor.getAuthenticationSession().setExecutionStatus(model.getId(), ClientSessionModel.ExecutionStatus.SUCCESS);
+                    processor.getAuthenticationSession().setExecutionStatus(model.getId(), AuthenticationSessionModel.ExecutionStatus.SUCCESS);
                     if (model.isAlternative()) alternativeSuccessful = true;
                     return processFlow();
                 } else {
@@ -115,7 +115,7 @@ public class DefaultAuthenticationFlow implements AuthenticationFlow {
             }
             if (model.isAlternative() && alternativeSuccessful) {
                 logger.debug("Skip alternative execution");
-                processor.getAuthenticationSession().setExecutionStatus(model.getId(), ClientSessionModel.ExecutionStatus.SKIPPED);
+                processor.getAuthenticationSession().setExecutionStatus(model.getId(), AuthenticationSessionModel.ExecutionStatus.SKIPPED);
                 continue;
             }
             if (model.isAuthenticatorFlow()) {
@@ -123,7 +123,7 @@ public class DefaultAuthenticationFlow implements AuthenticationFlow {
                 AuthenticationFlow authenticationFlow = processor.createFlowExecution(model.getFlowId(), model);
                 Response flowChallenge = authenticationFlow.processFlow();
                 if (flowChallenge == null) {
-                    processor.getAuthenticationSession().setExecutionStatus(model.getId(), ClientSessionModel.ExecutionStatus.SUCCESS);
+                    processor.getAuthenticationSession().setExecutionStatus(model.getId(), AuthenticationSessionModel.ExecutionStatus.SUCCESS);
                     if (model.isAlternative()) alternativeSuccessful = true;
                     continue;
                 } else {
@@ -131,13 +131,13 @@ public class DefaultAuthenticationFlow implements AuthenticationFlow {
                         alternativeChallenge = flowChallenge;
                         challengedAlternativeExecution = model;
                     } else if (model.isRequired()) {
-                        processor.getAuthenticationSession().setExecutionStatus(model.getId(), ClientSessionModel.ExecutionStatus.CHALLENGED);
+                        processor.getAuthenticationSession().setExecutionStatus(model.getId(), AuthenticationSessionModel.ExecutionStatus.CHALLENGED);
                         return flowChallenge;
                     } else if (model.isOptional()) {
-                        processor.getAuthenticationSession().setExecutionStatus(model.getId(), ClientSessionModel.ExecutionStatus.SKIPPED);
+                        processor.getAuthenticationSession().setExecutionStatus(model.getId(), AuthenticationSessionModel.ExecutionStatus.SKIPPED);
                         continue;
                     } else {
-                        processor.getAuthenticationSession().setExecutionStatus(model.getId(), ClientSessionModel.ExecutionStatus.SKIPPED);
+                        processor.getAuthenticationSession().setExecutionStatus(model.getId(), AuthenticationSessionModel.ExecutionStatus.SKIPPED);
                         continue;
                     }
                     return flowChallenge;
@@ -154,7 +154,7 @@ public class DefaultAuthenticationFlow implements AuthenticationFlow {
 
             if (authenticator.requiresUser() && authUser == null) {
                 if (alternativeChallenge != null) {
-                    processor.getAuthenticationSession().setExecutionStatus(challengedAlternativeExecution.getId(), ClientSessionModel.ExecutionStatus.CHALLENGED);
+                    processor.getAuthenticationSession().setExecutionStatus(challengedAlternativeExecution.getId(), AuthenticationSessionModel.ExecutionStatus.CHALLENGED);
                     return alternativeChallenge;
                 }
                 throw new AuthenticationFlowException("authenticator: " + factory.getId(), AuthenticationFlowError.UNKNOWN_USER);
@@ -166,14 +166,14 @@ public class DefaultAuthenticationFlow implements AuthenticationFlow {
                     if (model.isRequired()) {
                         if (factory.isUserSetupAllowed()) {
                             logger.debugv("authenticator SETUP_REQUIRED: {0}", factory.getId());
-                            processor.getAuthenticationSession().setExecutionStatus(model.getId(), ClientSessionModel.ExecutionStatus.SETUP_REQUIRED);
+                            processor.getAuthenticationSession().setExecutionStatus(model.getId(), AuthenticationSessionModel.ExecutionStatus.SETUP_REQUIRED);
                             authenticator.setRequiredActions(processor.getSession(), processor.getRealm(), processor.getAuthenticationSession().getAuthenticatedUser());
                             continue;
                         } else {
                             throw new AuthenticationFlowException(AuthenticationFlowError.CREDENTIAL_SETUP_REQUIRED);
                         }
                     } else if (model.isOptional()) {
-                        processor.getAuthenticationSession().setExecutionStatus(model.getId(), ClientSessionModel.ExecutionStatus.SKIPPED);
+                        processor.getAuthenticationSession().setExecutionStatus(model.getId(), AuthenticationSessionModel.ExecutionStatus.SKIPPED);
                         continue;
                     }
                 }
@@ -198,13 +198,13 @@ public class DefaultAuthenticationFlow implements AuthenticationFlow {
         switch (status) {
             case SUCCESS:
                 logger.debugv("authenticator SUCCESS: {0}", execution.getAuthenticator());
-                processor.getAuthenticationSession().setExecutionStatus(execution.getId(), ClientSessionModel.ExecutionStatus.SUCCESS);
+                processor.getAuthenticationSession().setExecutionStatus(execution.getId(), AuthenticationSessionModel.ExecutionStatus.SUCCESS);
                 if (execution.isAlternative()) alternativeSuccessful = true;
                 return null;
             case FAILED:
                 logger.debugv("authenticator FAILED: {0}", execution.getAuthenticator());
                 processor.logFailure();
-                processor.getAuthenticationSession().setExecutionStatus(execution.getId(), ClientSessionModel.ExecutionStatus.FAILED);
+                processor.getAuthenticationSession().setExecutionStatus(execution.getId(), AuthenticationSessionModel.ExecutionStatus.FAILED);
                 if (result.getChallenge() != null) {
                     return sendChallenge(result, execution);
                 }
@@ -214,37 +214,37 @@ public class DefaultAuthenticationFlow implements AuthenticationFlow {
                 processor.getAuthenticationSession().setAuthNote(AuthenticationProcessor.CURRENT_AUTHENTICATION_EXECUTION, execution.getId());
                 throw new ForkFlowException(result.getSuccessMessage(), result.getErrorMessage());
             case FORCE_CHALLENGE:
-                processor.getAuthenticationSession().setExecutionStatus(execution.getId(), ClientSessionModel.ExecutionStatus.CHALLENGED);
+                processor.getAuthenticationSession().setExecutionStatus(execution.getId(), AuthenticationSessionModel.ExecutionStatus.CHALLENGED);
                 return sendChallenge(result, execution);
             case CHALLENGE:
                 logger.debugv("authenticator CHALLENGE: {0}", execution.getAuthenticator());
                 if (execution.isRequired()) {
-                    processor.getAuthenticationSession().setExecutionStatus(execution.getId(), ClientSessionModel.ExecutionStatus.CHALLENGED);
+                    processor.getAuthenticationSession().setExecutionStatus(execution.getId(), AuthenticationSessionModel.ExecutionStatus.CHALLENGED);
                     return sendChallenge(result, execution);
                 }
                 UserModel authenticatedUser = processor.getAuthenticationSession().getAuthenticatedUser();
                 if (execution.isOptional() && authenticatedUser != null && result.getAuthenticator().configuredFor(processor.getSession(), processor.getRealm(), authenticatedUser)) {
-                    processor.getAuthenticationSession().setExecutionStatus(execution.getId(), ClientSessionModel.ExecutionStatus.CHALLENGED);
+                    processor.getAuthenticationSession().setExecutionStatus(execution.getId(), AuthenticationSessionModel.ExecutionStatus.CHALLENGED);
                     return sendChallenge(result, execution);
                 }
                 if (execution.isAlternative()) {
                     alternativeChallenge = result.getChallenge();
                     challengedAlternativeExecution = execution;
                 } else {
-                    processor.getAuthenticationSession().setExecutionStatus(execution.getId(), ClientSessionModel.ExecutionStatus.SKIPPED);
+                    processor.getAuthenticationSession().setExecutionStatus(execution.getId(), AuthenticationSessionModel.ExecutionStatus.SKIPPED);
                 }
                 return null;
             case FAILURE_CHALLENGE:
                 logger.debugv("authenticator FAILURE_CHALLENGE: {0}", execution.getAuthenticator());
                 processor.logFailure();
-                processor.getAuthenticationSession().setExecutionStatus(execution.getId(), ClientSessionModel.ExecutionStatus.CHALLENGED);
+                processor.getAuthenticationSession().setExecutionStatus(execution.getId(), AuthenticationSessionModel.ExecutionStatus.CHALLENGED);
                 return sendChallenge(result, execution);
             case ATTEMPTED:
                 logger.debugv("authenticator ATTEMPTED: {0}", execution.getAuthenticator());
                 if (execution.getRequirement() == AuthenticationExecutionModel.Requirement.REQUIRED) {
                     throw new AuthenticationFlowException(AuthenticationFlowError.INVALID_CREDENTIALS);
                 }
-                processor.getAuthenticationSession().setExecutionStatus(execution.getId(), ClientSessionModel.ExecutionStatus.ATTEMPTED);
+                processor.getAuthenticationSession().setExecutionStatus(execution.getId(), AuthenticationSessionModel.ExecutionStatus.ATTEMPTED);
                 return null;
             case FLOW_RESET:
                 processor.resetFlow();
diff --git a/services/src/main/java/org/keycloak/authentication/FormAuthenticationFlow.java b/services/src/main/java/org/keycloak/authentication/FormAuthenticationFlow.java
index 0e121dd..955879f 100755
--- a/services/src/main/java/org/keycloak/authentication/FormAuthenticationFlow.java
+++ b/services/src/main/java/org/keycloak/authentication/FormAuthenticationFlow.java
@@ -24,7 +24,6 @@ import org.keycloak.events.EventBuilder;
 import org.keycloak.forms.login.LoginFormsProvider;
 import org.keycloak.models.AuthenticationExecutionModel;
 import org.keycloak.models.AuthenticatorConfigModel;
-import org.keycloak.models.ClientSessionModel;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.UserModel;
@@ -167,13 +166,13 @@ public class FormAuthenticationFlow implements AuthenticationFlow {
         if (!actionExecution.equals(formExecution.getId())) {
             throw new AuthenticationFlowException("action is not current execution", AuthenticationFlowError.INTERNAL_ERROR);
         }
-        Map<String, ClientSessionModel.ExecutionStatus> executionStatus = new HashMap<>();
+        Map<String, AuthenticationSessionModel.ExecutionStatus> executionStatus = new HashMap<>();
         List<FormAction> requiredActions = new LinkedList<>();
         List<ValidationContextImpl> successes = new LinkedList<>();
         List<ValidationContextImpl> errors = new LinkedList<>();
         for (AuthenticationExecutionModel formActionExecution : formActionExecutions) {
             if (!formActionExecution.isEnabled()) {
-                executionStatus.put(formActionExecution.getId(), ClientSessionModel.ExecutionStatus.SKIPPED);
+                executionStatus.put(formActionExecution.getId(), AuthenticationSessionModel.ExecutionStatus.SKIPPED);
                 continue;
             }
             FormActionFactory factory = (FormActionFactory)processor.getSession().getKeycloakSessionFactory().getProviderFactory(FormAction.class, formActionExecution.getAuthenticator());
@@ -190,14 +189,14 @@ public class FormAuthenticationFlow implements AuthenticationFlow {
                     if (formActionExecution.isRequired()) {
                         if (factory.isUserSetupAllowed()) {
                             AuthenticationProcessor.logger.debugv("authenticator SETUP_REQUIRED: {0}", formExecution.getAuthenticator());
-                            executionStatus.put(formActionExecution.getId(), ClientSessionModel.ExecutionStatus.SETUP_REQUIRED);
+                            executionStatus.put(formActionExecution.getId(), AuthenticationSessionModel.ExecutionStatus.SETUP_REQUIRED);
                             requiredActions.add(action);
                             continue;
                         } else {
                             throw new AuthenticationFlowException(AuthenticationFlowError.CREDENTIAL_SETUP_REQUIRED);
                         }
                     } else if (formActionExecution.isOptional()) {
-                        executionStatus.put(formActionExecution.getId(), ClientSessionModel.ExecutionStatus.SKIPPED);
+                        executionStatus.put(formActionExecution.getId(), AuthenticationSessionModel.ExecutionStatus.SKIPPED);
                         continue;
                     }
                 }
@@ -206,10 +205,10 @@ public class FormAuthenticationFlow implements AuthenticationFlow {
             ValidationContextImpl result = new ValidationContextImpl(formActionExecution, action);
             action.validate(result);
             if (result.success) {
-                executionStatus.put(formActionExecution.getId(), ClientSessionModel.ExecutionStatus.SUCCESS);
+                executionStatus.put(formActionExecution.getId(), AuthenticationSessionModel.ExecutionStatus.SUCCESS);
                 successes.add(result);
             } else {
-                executionStatus.put(formActionExecution.getId(), ClientSessionModel.ExecutionStatus.CHALLENGED);
+                executionStatus.put(formActionExecution.getId(), AuthenticationSessionModel.ExecutionStatus.CHALLENGED);
                 errors.add(result);
             }
         }
@@ -235,14 +234,14 @@ public class FormAuthenticationFlow implements AuthenticationFlow {
             context.action.success(context);
         }
         // set status and required actions only if form is fully successful
-        for (Map.Entry<String, ClientSessionModel.ExecutionStatus> entry : executionStatus.entrySet()) {
+        for (Map.Entry<String, AuthenticationSessionModel.ExecutionStatus> entry : executionStatus.entrySet()) {
             processor.getAuthenticationSession().setExecutionStatus(entry.getKey(), entry.getValue());
         }
         for (FormAction action : requiredActions) {
             action.setRequiredActions(processor.getSession(), processor.getRealm(), processor.getAuthenticationSession().getAuthenticatedUser());
 
         }
-        processor.getAuthenticationSession().setExecutionStatus(actionExecution, ClientSessionModel.ExecutionStatus.SUCCESS);
+        processor.getAuthenticationSession().setExecutionStatus(actionExecution, AuthenticationSessionModel.ExecutionStatus.SUCCESS);
         processor.getAuthenticationSession().removeAuthNote(AuthenticationProcessor.CURRENT_AUTHENTICATION_EXECUTION);
         return null;
     }
diff --git a/services/src/main/java/org/keycloak/authentication/requiredactions/VerifyEmail.java b/services/src/main/java/org/keycloak/authentication/requiredactions/VerifyEmail.java
index fd3ce48..f3ea22f 100755
--- a/services/src/main/java/org/keycloak/authentication/requiredactions/VerifyEmail.java
+++ b/services/src/main/java/org/keycloak/authentication/requiredactions/VerifyEmail.java
@@ -69,17 +69,14 @@ public class VerifyEmail implements RequiredActionProvider, RequiredActionFactor
             return;
         }
 
-        LoginFormsProvider loginFormsProvider = context.getSession().getProvider(LoginFormsProvider.class)
-                .setClientSessionCode(context.generateCode())
-                .setAuthenticationSession(authSession)
-                .setUser(context.getUser());
+        LoginFormsProvider loginFormsProvider = context.form();
         Response challenge;
 
         // Do not allow resending e-mail by simple page refresh, i.e. when e-mail sent, it should be resent properly via email-verification endpoint
         if (! Objects.equals(authSession.getAuthNote(Constants.VERIFY_EMAIL_KEY), email)) {
             authSession.setAuthNote(Constants.VERIFY_EMAIL_KEY, email);
             context.getEvent().clone().event(EventType.SEND_VERIFY_EMAIL).detail(Details.EMAIL, email).success();
-            challenge = sendVerifyEmail(context.getSession(), context.generateCode(), context.getUser(), context.getAuthenticationSession());
+            challenge = sendVerifyEmail(context.getSession(), loginFormsProvider, context.getUser(), context.getAuthenticationSession());
         } else {
             challenge = loginFormsProvider.createResponse(UserModel.RequiredAction.VERIFY_EMAIL);
         }
@@ -87,9 +84,15 @@ public class VerifyEmail implements RequiredActionProvider, RequiredActionFactor
         context.challenge(challenge);
     }
 
+
     @Override
     public void processAction(RequiredActionContext context) {
-        context.failure();
+        logger.infof("Re-sending email requested for user: %s", context.getUser().getUsername());
+
+        // This will allow user to re-send email again
+        context.getAuthenticationSession().removeAuthNote(Constants.VERIFY_EMAIL_KEY);
+
+        requiredActionChallenge(context);
     }
 
 
@@ -124,15 +127,10 @@ public class VerifyEmail implements RequiredActionProvider, RequiredActionFactor
         return UserModel.RequiredAction.VERIFY_EMAIL.name();
     }
 
-    public static Response sendVerifyEmail(KeycloakSession session, String clientCode, UserModel user, AuthenticationSessionModel authSession) throws UriBuilderException, IllegalArgumentException {
+    private Response sendVerifyEmail(KeycloakSession session, LoginFormsProvider forms, UserModel user, AuthenticationSessionModel authSession) throws UriBuilderException, IllegalArgumentException {
         RealmModel realm = session.getContext().getRealm();
         UriInfo uriInfo = session.getContext().getUri();
 
-        LoginFormsProvider forms = session.getProvider(LoginFormsProvider.class)
-          .setClientSessionCode(clientCode)
-          .setAuthenticationSession(authSession)
-          .setUser(authSession.getAuthenticatedUser());
-
         int validityInSecs = realm.getAccessCodeLifespanUserAction();
         int absoluteExpirationInSecs = Time.currentTime() + validityInSecs;
 //        ExecuteActionsActionToken token = new ExecuteActionsActionToken(user.getId(), absoluteExpirationInSecs, null,
diff --git a/services/src/main/java/org/keycloak/authorization/common/KeycloakIdentity.java b/services/src/main/java/org/keycloak/authorization/common/KeycloakIdentity.java
index 6b5b027..9ea53e2 100644
--- a/services/src/main/java/org/keycloak/authorization/common/KeycloakIdentity.java
+++ b/services/src/main/java/org/keycloak/authorization/common/KeycloakIdentity.java
@@ -23,7 +23,6 @@ import org.keycloak.authorization.attribute.Attributes;
 import org.keycloak.authorization.identity.Identity;
 import org.keycloak.authorization.util.Tokens;
 import org.keycloak.models.ClientModel;
-import org.keycloak.models.ClientSessionModel;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.UserModel;
@@ -118,8 +117,8 @@ public class KeycloakIdentity implements Identity {
     @Override
     public String getId() {
         if (isResourceServer()) {
-            ClientSessionModel clientSession = this.keycloakSession.sessions().getClientSession(this.accessToken.getClientSession());
-            return clientSession.getClient().getId();
+            ClientModel client = getTargetClient();
+            return client==null ? null : client.getId();
         }
 
         return this.accessToken.getSubject();
@@ -137,20 +136,10 @@ public class KeycloakIdentity implements Identity {
     private  boolean isResourceServer() {
         UserModel clientUser = null;
 
-        if (this.accessToken.getClientSession() != null) {
-            ClientSessionModel clientSession = this.keycloakSession.sessions().getClientSession(this.accessToken.getClientSession());
+        ClientModel clientModel = getTargetClient();
 
-            if (clientSession != null) {
-                clientUser = this.keycloakSession.users().getServiceAccount(clientSession.getClient());
-            }
-        }
-
-        if (this.accessToken.getIssuedFor() != null) {
-            ClientModel clientModel = this.keycloakSession.realms().getClientById(this.accessToken.getIssuedFor(), this.realm);
-
-            if (clientModel != null) {
-                clientUser = this.keycloakSession.users().getServiceAccount(clientModel);
-            }
+        if (clientModel != null) {
+            clientUser = this.keycloakSession.users().getServiceAccount(clientModel);
         }
 
         if (clientUser == null) {
@@ -159,4 +148,17 @@ public class KeycloakIdentity implements Identity {
 
         return this.accessToken.getSubject().equals(clientUser.getId());
     }
+
+    private ClientModel getTargetClient() {
+        if (this.accessToken.getIssuedFor() != null) {
+            return realm.getClientByClientId(accessToken.getIssuedFor());
+        }
+
+        if (this.accessToken.getAudience() != null && this.accessToken.getAudience().length > 0) {
+            String audience = this.accessToken.getAudience()[0];
+            return realm.getClientByClientId(audience);
+        }
+
+        return null;
+    }
 }
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 4f24373..45183c3 100755
--- a/services/src/main/java/org/keycloak/broker/oidc/OIDCIdentityProvider.java
+++ b/services/src/main/java/org/keycloak/broker/oidc/OIDCIdentityProvider.java
@@ -32,7 +32,6 @@ import org.keycloak.jose.jws.JWSInput;
 import org.keycloak.jose.jws.JWSInputException;
 import org.keycloak.jose.jws.crypto.RSAProvider;
 import org.keycloak.keys.loader.PublicKeyStorageManager;
-import org.keycloak.models.ClientSessionModel;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.UserSessionModel;
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 5f02b8e..51d6eb8 100755
--- a/services/src/main/java/org/keycloak/broker/saml/SAMLIdentityProvider.java
+++ b/services/src/main/java/org/keycloak/broker/saml/SAMLIdentityProvider.java
@@ -31,7 +31,6 @@ import org.keycloak.dom.saml.v2.assertion.SubjectType;
 import org.keycloak.dom.saml.v2.protocol.ResponseType;
 import org.keycloak.events.EventBuilder;
 import org.keycloak.keys.RsaKeyMetadata;
-import org.keycloak.models.ClientSessionModel;
 import org.keycloak.models.FederatedIdentityModel;
 import org.keycloak.models.KeyManager;
 import org.keycloak.models.KeycloakSession;
diff --git a/services/src/main/java/org/keycloak/forms/account/freemarker/model/SessionsBean.java b/services/src/main/java/org/keycloak/forms/account/freemarker/model/SessionsBean.java
index a474f4f..f597d5c 100755
--- a/services/src/main/java/org/keycloak/forms/account/freemarker/model/SessionsBean.java
+++ b/services/src/main/java/org/keycloak/forms/account/freemarker/model/SessionsBean.java
@@ -19,7 +19,6 @@ package org.keycloak.forms.account.freemarker.model;
 
 import org.keycloak.common.util.Time;
 import org.keycloak.models.ClientModel;
-import org.keycloak.models.ClientSessionModel;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.UserSessionModel;
 
@@ -79,8 +78,8 @@ public class SessionsBean {
 
         public Set<String> getClients() {
             Set<String> clients = new HashSet<String>();
-            for (ClientSessionModel clientSession : session.getClientSessions()) {
-                ClientModel client = clientSession.getClient();
+            for (String clientUUID : session.getAuthenticatedClientSessions().keySet()) {
+                ClientModel client = realm.getClientById(clientUUID);
                 clients.add(client.getClientId());
             }
             return clients;
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 e93df33..625406c 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
@@ -90,7 +90,6 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
 
     private UserModel user;
 
-    private AuthenticationSessionModel authenticationSession;
     private final Map<String, Object> attributes = new HashMap<String, Object>();
 
     public FreeMarkerLoginFormsProvider(KeycloakSession session, FreeMarkerUtil freeMarker) {
@@ -156,6 +155,13 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
             uriBuilder.replaceQuery(null);
         }
 
+        URI baseUri = uriBuilder.build();
+
+        if (accessCode != null) {
+            uriBuilder.queryParam(OAuth2Constants.CODE, accessCode);
+        }
+        URI baseUriWithCode = uriBuilder.build();
+
         for (String k : queryParameterMap.keySet()) {
 
             Object[] objects = queryParameterMap.get(k).toArray();
@@ -163,13 +169,6 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
             uriBuilder.replaceQueryParam(k, objects);
         }
 
-        // TODO:hmlnarik Why was the following removed in https://github.com/hmlnarik/keycloak/commit/6df8f13109d6ea77b455e04d884994e5831ea52b#diff-d795b851c2db89d5198c897aba4c40c9
-        if (accessCode != null) {
-            uriBuilder.replaceQueryParam(OAuth2Constants.CODE, accessCode);
-        }
-
-        URI baseUri = uriBuilder.build();
-
         ThemeProvider themeProvider = session.getProvider(ThemeProvider.class, "extending");
         Theme theme;
         try {
@@ -221,7 +220,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));
+            attributes.put("social", new IdentityProviderBean(realm, session, identityProviders, baseUriWithCode));
 
             attributes.put("url", new UrlBean(realm, theme, baseUri, this.actionUri));
 
@@ -313,9 +312,7 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
             if (objects.length == 1 && objects[0] == null) continue; //
             uriBuilder.replaceQueryParam(k, objects);
         }
-        if (accessCode != null) {
-            uriBuilder.replaceQueryParam(OAuth2Constants.CODE, accessCode);
-        }
+
         URI baseUri = uriBuilder.build();
 
         if (accessCode != null) {
@@ -573,12 +570,6 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
     }
 
     @Override
-    public LoginFormsProvider setAuthenticationSession(AuthenticationSessionModel authSession) {
-        this.authenticationSession = authSession;
-        return this;
-    }
-
-    @Override
     public LoginFormsProvider setAccessRequest(List<RoleModel> realmRolesRequested, MultivaluedMap<String, RoleModel> resourceRolesRequested, List<ProtocolMapperModel> protocolMappersRequested) {
         this.realmRolesRequested = realmRolesRequested;
         this.resourceRolesRequested = resourceRolesRequested;
diff --git a/services/src/main/java/org/keycloak/forms/login/freemarker/model/UrlBean.java b/services/src/main/java/org/keycloak/forms/login/freemarker/model/UrlBean.java
index 9b3a9f3..0c574c1 100755
--- a/services/src/main/java/org/keycloak/forms/login/freemarker/model/UrlBean.java
+++ b/services/src/main/java/org/keycloak/forms/login/freemarker/model/UrlBean.java
@@ -85,10 +85,6 @@ public class UrlBean {
         return Urls.loginUsernameReminder(baseURI, realm).toString();
     }
 
-    public String getLoginEmailVerificationUrl() {
-        return Urls.loginActionEmailVerification(baseURI, realm).toString();
-    }
-
     public String getFirstBrokerLoginUrl() {
         return Urls.firstBrokerLoginProcessor(baseURI, realm).toString();
     }
diff --git a/services/src/main/java/org/keycloak/protocol/AuthorizationEndpointBase.java b/services/src/main/java/org/keycloak/protocol/AuthorizationEndpointBase.java
index 89680bb..9c1e5a5 100755
--- a/services/src/main/java/org/keycloak/protocol/AuthorizationEndpointBase.java
+++ b/services/src/main/java/org/keycloak/protocol/AuthorizationEndpointBase.java
@@ -21,7 +21,6 @@ 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;
@@ -35,7 +34,7 @@ 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.services.util.AuthenticationFlowURLHelper;
 import org.keycloak.sessions.AuthenticationSessionModel;
 
 import javax.ws.rs.core.Context;
@@ -159,7 +158,7 @@ public abstract class AuthorizationEndpointBase {
             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());
+                logger.debugf("Authentication session '%s' exists, but is expired. Restart existing authentication session", authSession.getId());
                 authSession.restartSession(realm, client);
                 return new AuthorizationEndpointChecks(authSession);
 
@@ -167,10 +166,10 @@ public abstract class AuthorizationEndpointBase {
                 // 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");
+                    logger.debug("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");
+                    logger.debug("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);
                 }
@@ -178,7 +177,7 @@ public abstract class AuthorizationEndpointBase {
                 return new AuthorizationEndpointChecks(authSession);
 
             } else {
-                logger.info("Re-sent some previous request to Authorization endpoint. Likely browser 'back' or 'refresh' button.");
+                logger.debug("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)) {
@@ -186,7 +185,7 @@ public abstract class AuthorizationEndpointBase {
                 } else {
                     CacheControlUtil.noBackButtonCacheControlHeader();
 
-                    Response response = new PageExpiredRedirect(session, realm, uriInfo)
+                    Response response = new AuthenticationFlowURLHelper(session, realm, uriInfo)
                             .showPageExpired(authSession);
                     return new AuthorizationEndpointChecks(response);
                 }
@@ -196,11 +195,11 @@ public abstract class AuthorizationEndpointBase {
         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);
+            logger.debugf("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());
+            logger.debugf("Sent request to authz endpoint. Created new authentication session with ID '%s'", authSession.getId());
         }
 
         return new AuthorizationEndpointChecks(authSession);
@@ -224,17 +223,13 @@ public abstract class AuthorizationEndpointBase {
         }
 
         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;
-            }
+        // Check if we transitted between flows (eg. clicking "register" on login screen and then clicking browser 'back', which showed this page)
+        if (!initialFlow.equals(lastFlow) && AuthenticationSessionModel.Action.AUTHENTICATE.toString().equals(authSession.getAction())) {
+            logger.debugf("Transition between flows! Current flow: %s, Previous flow: %s", initialFlow, lastFlow);
+
+            authSession.setAuthNote(AuthenticationProcessor.CURRENT_FLOW_PATH, initialFlow);
+            authSession.removeAuthNote(AuthenticationProcessor.CURRENT_AUTHENTICATION_EXECUTION);
+            return false;
         }
 
         return false;
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 0cd0219..26d012b 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
@@ -28,7 +28,6 @@ import org.keycloak.events.EventBuilder;
 import org.keycloak.events.EventType;
 import org.keycloak.models.AuthenticationFlowModel;
 import org.keycloak.models.ClientModel;
-import org.keycloak.models.ClientSessionModel;
 import org.keycloak.models.RealmModel;
 import org.keycloak.protocol.AuthorizationEndpointBase;
 import org.keycloak.protocol.oidc.OIDCLoginProtocol;
@@ -41,7 +40,6 @@ 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;
@@ -387,7 +385,7 @@ public class AuthorizationEndpoint extends AuthorizationEndpointBase {
     private void updateAuthenticationSession() {
         authenticationSession.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL);
         authenticationSession.setRedirectUri(redirectUri);
-        authenticationSession.setAction(ClientSessionModel.Action.AUTHENTICATE.name());
+        authenticationSession.setAction(AuthenticationSessionModel.Action.AUTHENTICATE.name());
         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()));
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 d564840..83570ef 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
@@ -210,12 +210,13 @@ public class TokenEndpoint {
             throw new ErrorResponseException(OAuthErrorException.INVALID_REQUEST, "Missing parameter: " + OAuth2Constants.CODE, Response.Status.BAD_REQUEST);
         }
 
+        String[] parts = code.split("\\.");
+        if (parts.length == 4) {
+            event.detail(Details.CODE_ID, parts[2]);
+        }
+
         ClientSessionCode.ParseResult<AuthenticatedClientSessionModel> parseResult = ClientSessionCode.parseResult(code, session, realm, AuthenticatedClientSessionModel.class);
         if (parseResult.isAuthSessionNotFound() || parseResult.isIllegalHash()) {
-            String[] parts = code.split("\\.");
-            if (parts.length == 2) {
-                event.detail(Details.CODE_ID, parts[1]);
-            }
             event.error(Errors.INVALID_CODE);
 
             // Attempt to use same code twice should invalidate existing clientSession
@@ -228,17 +229,16 @@ public class TokenEndpoint {
         }
 
         AuthenticatedClientSessionModel clientSession = parseResult.getClientSession();
-        event.detail(Details.CODE_ID, clientSession.getId());
 
         if (!parseResult.getCode().isValid(AuthenticatedClientSessionModel.Action.CODE_TO_TOKEN.name(), ClientSessionCode.ActionType.CLIENT)) {
             event.error(Errors.INVALID_CODE);
             throw new ErrorResponseException(OAuthErrorException.INVALID_GRANT, "Code is expired", Response.Status.BAD_REQUEST);
         }
 
-        // TODO: This shouldn't be needed to write into the clientLoginSessionModel itself
+        // TODO: This shouldn't be needed to write into the AuthenticatedClientSessionModel itself
         parseResult.getCode().setAction(null);
 
-        // TODO: Maybe rather create userSession even at this stage? Not sure...
+        // TODO: Maybe rather create userSession even at this stage?
         UserSessionModel userSession = clientSession.getUserSession();
 
         if (userSession == null) {
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/mappers/AbstractUserRoleMappingMapper.java b/services/src/main/java/org/keycloak/protocol/oidc/mappers/AbstractUserRoleMappingMapper.java
index f4ef89d..d239951 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/mappers/AbstractUserRoleMappingMapper.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/mappers/AbstractUserRoleMappingMapper.java
@@ -93,9 +93,9 @@ abstract class AbstractUserRoleMappingMapper extends AbstractOIDCProtocolMapper 
         // get a set of all realm roles assigned to the user or its group
         Stream<RoleModel> clientUserRoles = getAllUserRolesStream(user).filter(restriction);
 
-        boolean dontLimitScope = userSession.getClientSessions().stream().anyMatch(cs -> cs.getClient().isFullScopeAllowed());
+        boolean dontLimitScope = userSession.getAuthenticatedClientSessions().values().stream().anyMatch(cs -> cs.getClient().isFullScopeAllowed());
         if (! dontLimitScope) {
-            Set<RoleModel> clientRoles = userSession.getClientSessions().stream()
+            Set<RoleModel> clientRoles = userSession.getAuthenticatedClientSessions().values().stream()
               .flatMap(cs -> cs.getClient().getScopeMappings().stream())
               .collect(Collectors.toSet());
 
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 93df9b0..e7c147d 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java
@@ -575,7 +575,6 @@ public class TokenManager {
 
     protected AccessToken initToken(RealmModel realm, ClientModel client, UserModel user, UserSessionModel session, AuthenticatedClientSessionModel clientSession, UriInfo uriInfo) {
         AccessToken token = new AccessToken();
-        token.clientSession(clientSession.getId());
         token.id(KeycloakModelUtils.generateId());
         token.type(TokenUtil.TOKEN_TYPE_BEARER);
         token.subject(user.getId());
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 10e45a2..a8218c1 100755
--- a/services/src/main/java/org/keycloak/protocol/saml/SamlProtocol.java
+++ b/services/src/main/java/org/keycloak/protocol/saml/SamlProtocol.java
@@ -376,8 +376,12 @@ public class SamlProtocol implements LoginProtocol {
         clientSession.setNote(SAML_NAME_ID_FORMAT, nameIdFormat);
 
         SAML2LoginResponseBuilder builder = new SAML2LoginResponseBuilder();
-        builder.requestID(requestID).destination(redirectUri).issuer(responseIssuer).assertionExpiration(realm.getAccessCodeLifespan()).subjectExpiration(realm.getAccessTokenLifespan()).sessionIndex(clientSession.getId())
+        builder.requestID(requestID).destination(redirectUri).issuer(responseIssuer).assertionExpiration(realm.getAccessCodeLifespan()).subjectExpiration(realm.getAccessTokenLifespan())
                 .requestIssuer(clientSession.getClient().getClientId()).nameIdentifier(nameIdFormat, nameId).authMethod(JBossSAMLURIConstants.AC_UNSPECIFIED.get());
+
+        String sessionIndex = SamlSessionUtils.getSessionIndex(clientSession);
+        builder.sessionIndex(sessionIndex);
+
         if (!samlClient.includeAuthnStatement()) {
             builder.disableAuthnStatement(true);
         }
@@ -682,8 +686,12 @@ public class SamlProtocol implements LoginProtocol {
 
     protected SAML2LogoutRequestBuilder createLogoutRequest(String logoutUrl, AuthenticatedClientSessionModel clientSession, ClientModel client) {
         // build userPrincipal with subject used at login
-        SAML2LogoutRequestBuilder logoutBuilder = new SAML2LogoutRequestBuilder().assertionExpiration(realm.getAccessCodeLifespan()).issuer(getResponseIssuer(realm)).sessionIndex(clientSession.getId())
+        SAML2LogoutRequestBuilder logoutBuilder = new SAML2LogoutRequestBuilder().assertionExpiration(realm.getAccessCodeLifespan()).issuer(getResponseIssuer(realm))
                 .userPrincipal(clientSession.getNote(SAML_NAME_ID), clientSession.getNote(SAML_NAME_ID_FORMAT)).destination(logoutUrl);
+
+        String sessionIndex = SamlSessionUtils.getSessionIndex(clientSession);
+        logoutBuilder.sessionIndex(sessionIndex);
+
         return logoutBuilder;
     }
 
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 f81d25d..9a6790b 100755
--- a/services/src/main/java/org/keycloak/protocol/saml/SamlService.java
+++ b/services/src/main/java/org/keycloak/protocol/saml/SamlService.java
@@ -37,8 +37,8 @@ import org.keycloak.events.Errors;
 import org.keycloak.events.EventBuilder;
 import org.keycloak.events.EventType;
 import org.keycloak.keys.RsaKeyMetadata;
+import org.keycloak.models.AuthenticatedClientSessionModel;
 import org.keycloak.models.ClientModel;
-import org.keycloak.models.ClientSessionModel;
 import org.keycloak.models.KeyManager;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.RealmModel;
@@ -55,7 +55,6 @@ 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;
@@ -99,11 +98,6 @@ public class SamlService extends AuthorizationEndpointBase {
 
     protected static final Logger logger = Logger.getLogger(SamlService.class);
 
-    @Context
-    protected KeycloakSession session;
-
-    private String requestRelayState;
-
     public SamlService(RealmModel realm, EventBuilder event) {
         super(realm, event);
     }
@@ -283,7 +277,7 @@ public class SamlService extends AuthorizationEndpointBase {
 
             authSession.setProtocol(SamlProtocol.LOGIN_PROTOCOL);
             authSession.setRedirectUri(redirect);
-            authSession.setAction(ClientSessionModel.Action.AUTHENTICATE.name());
+            authSession.setAction(AuthenticationSessionModel.Action.AUTHENTICATE.name());
             authSession.setClientNote(SamlProtocol.SAML_BINDING, bindingType);
             authSession.setClientNote(GeneralConstants.RELAY_STATE, relayState);
             authSession.setClientNote(SamlProtocol.SAML_REQUEST_ID, requestAbstractType.getID());
@@ -378,31 +372,22 @@ public class SamlService extends AuthorizationEndpointBase {
                 userSession.setNote(SamlProtocol.SAML_LOGOUT_CANONICALIZATION, samlClient.getCanonicalizationMethod());
                 userSession.setNote(AuthenticationManager.KEYCLOAK_LOGOUT_PROTOCOL, SamlProtocol.LOGIN_PROTOCOL);
                 // remove client from logout requests
-                for (ClientSessionModel clientSession : userSession.getClientSessions()) {
-                    if (clientSession.getClient().getId().equals(client.getId())) {
-                        clientSession.setAction(ClientSessionModel.Action.LOGGED_OUT.name());
-                    }
+                AuthenticatedClientSessionModel clientSession = userSession.getAuthenticatedClientSessions().get(client.getId());
+                if (clientSession != null) {
+                    clientSession.setAction(AuthenticationSessionModel.Action.LOGGED_OUT.name());
                 }
                 logger.debug("browser Logout");
                 return authManager.browserLogout(session, realm, userSession, uriInfo, clientConnection, headers);
             } else if (logoutRequest.getSessionIndex() != null) {
                 for (String sessionIndex : logoutRequest.getSessionIndex()) {
-                    ClientSessionModel clientSession = session.sessions().getClientSession(realm, sessionIndex);
+
+                    AuthenticatedClientSessionModel clientSession = SamlSessionUtils.getClientSession(session, realm, sessionIndex);
                     if (clientSession == null)
                         continue;
                     UserSessionModel userSession = clientSession.getUserSession();
                     if (clientSession.getClient().getClientId().equals(client.getClientId())) {
                         // remove requesting client from logout
-                        clientSession.setAction(ClientSessionModel.Action.LOGGED_OUT.name());
-
-                        // Remove also other clientSessions of this client as there could be more in this UserSession
-                        if (userSession != null) {
-                            for (ClientSessionModel clientSession2 : userSession.getClientSessions()) {
-                                if (clientSession2.getClient().getId().equals(client.getId())) {
-                                    clientSession2.setAction(ClientSessionModel.Action.LOGGED_OUT.name());
-                                }
-                            }
-                        }
+                        clientSession.setAction(AuthenticationSessionModel.Action.LOGGED_OUT.name());
                     }
 
                     try {
@@ -609,6 +594,10 @@ public class SamlService extends AuthorizationEndpointBase {
             event.error(Errors.CLIENT_NOT_FOUND);
             return ErrorPage.error(session, Messages.CLIENT_NOT_FOUND);
         }
+        if (!client.isEnabled()) {
+            event.error(Errors.CLIENT_DISABLED);
+            return ErrorPage.error(session, Messages.CLIENT_DISABLED);
+        }
         if (client.getManagementUrl() == null && client.getAttribute(SamlProtocol.SAML_ASSERTION_CONSUMER_URL_POST_ATTRIBUTE) == null && client.getAttribute(SamlProtocol.SAML_ASSERTION_CONSUMER_URL_REDIRECT_ATTRIBUTE) == null) {
             logger.error("SAML assertion consumer url not set up");
             event.error(Errors.INVALID_REDIRECT_URI);
@@ -654,7 +643,7 @@ public class SamlService extends AuthorizationEndpointBase {
 
         AuthenticationSessionModel authSession = checks.authSession;
         authSession.setProtocol(SamlProtocol.LOGIN_PROTOCOL);
-        authSession.setAction(ClientSessionModel.Action.AUTHENTICATE.name());
+        authSession.setAction(AuthenticationSessionModel.Action.AUTHENTICATE.name());
         authSession.setClientNote(SamlProtocol.SAML_BINDING, SamlProtocol.SAML_POST_BINDING);
         authSession.setClientNote(SamlProtocol.SAML_IDP_INITIATED_LOGIN, "true");
         authSession.setRedirectUri(redirect);
diff --git a/services/src/main/java/org/keycloak/protocol/saml/SamlSessionUtils.java b/services/src/main/java/org/keycloak/protocol/saml/SamlSessionUtils.java
new file mode 100644
index 0000000..0083fdc
--- /dev/null
+++ b/services/src/main/java/org/keycloak/protocol/saml/SamlSessionUtils.java
@@ -0,0 +1,65 @@
+/*
+ * 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.protocol.saml;
+
+import java.util.regex.Pattern;
+
+import org.keycloak.models.AuthenticatedClientSessionModel;
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserSessionModel;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class SamlSessionUtils {
+
+    private static final String DELIMITER = "::";
+
+    // Just perf optimization
+    private static final Pattern PATTERN = Pattern.compile(DELIMITER);
+
+
+    public static String getSessionIndex(AuthenticatedClientSessionModel clientSession) {
+        UserSessionModel userSession = clientSession.getUserSession();
+        ClientModel client = clientSession.getClient();
+
+        return userSession.getId() + DELIMITER + client.getId();
+    }
+
+
+    public static AuthenticatedClientSessionModel getClientSession(KeycloakSession session, RealmModel realm, String sessionIndex) {
+        if (sessionIndex == null) {
+            return null;
+        }
+
+        String[] parts = PATTERN.split(sessionIndex);
+        if (parts.length != 2) {
+            return null;
+        }
+
+        UserSessionModel userSession = session.sessions().getUserSession(realm, parts[0]);
+        if (userSession == null) {
+            return null;
+        }
+
+        return userSession.getAuthenticatedClientSessions().get(parts[1]);
+    }
+
+}
diff --git a/services/src/main/java/org/keycloak/services/ErrorPageException.java b/services/src/main/java/org/keycloak/services/ErrorPageException.java
index 4bcbbc8..51ee9c8 100644
--- a/services/src/main/java/org/keycloak/services/ErrorPageException.java
+++ b/services/src/main/java/org/keycloak/services/ErrorPageException.java
@@ -37,6 +37,8 @@ public class ErrorPageException extends WebApplicationException {
         this.parameters = parameters;
     }
 
+
+
     @Override
     public Response getResponse() {
         return ErrorPage.error(session, errorMessage, parameters);
diff --git a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
index 0ba5d77..6e7a917 100755
--- a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
@@ -537,7 +537,7 @@ public class AuthenticationManager {
                     .createInfoPage();
             return response;
 
-            // TODO:mposolda doublecheck if restart-cookie and authentication session are cleared in this flow
+            // Don't remove authentication session for now, to ensure that browser buttons (back/refresh) will still work fine.
 
         }
         RealmModel realm = authSession.getRealm();
diff --git a/services/src/main/java/org/keycloak/services/managers/AuthenticationSessionManager.java b/services/src/main/java/org/keycloak/services/managers/AuthenticationSessionManager.java
index 04271f1..0297f13 100644
--- a/services/src/main/java/org/keycloak/services/managers/AuthenticationSessionManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/AuthenticationSessionManager.java
@@ -74,20 +74,17 @@ public class AuthenticationSessionManager {
         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);
+        log.debugf("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");
-            }
+        if (cookieVal != null) {
+            log.debugf("Found AUTH_SESSION_ID cookie with value %s", cookieVal);
+        } else {
+            log.debugf("Not found AUTH_SESSION_ID cookie");
         }
 
         return cookieVal;
@@ -95,7 +92,7 @@ public class AuthenticationSessionManager {
 
 
     public void removeAuthenticationSession(RealmModel realm, AuthenticationSessionModel authSession, boolean expireRestartCookie) {
-        log.infof("Removing authSession '%s'. Expire restart cookie: %b", authSession.getId(), expireRestartCookie);
+        log.debugf("Removing authSession '%s'. Expire restart cookie: %b", authSession.getId(), expireRestartCookie);
         session.authenticationSessions().removeAuthenticationSession(realm, authSession);
 
         // expire restart cookie
diff --git a/services/src/main/java/org/keycloak/services/managers/ClientManager.java b/services/src/main/java/org/keycloak/services/managers/ClientManager.java
index fec49c9..1bcfaf5 100644
--- a/services/src/main/java/org/keycloak/services/managers/ClientManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/ClientManager.java
@@ -39,6 +39,7 @@ import org.keycloak.protocol.oidc.mappers.UserSessionNoteMapper;
 import org.keycloak.representations.adapters.config.BaseRealmConfig;
 import org.keycloak.representations.adapters.config.PolicyEnforcerConfig;
 import org.keycloak.representations.idm.ClientRepresentation;
+import org.keycloak.sessions.AuthenticationSessionProvider;
 
 import java.net.URI;
 import java.util.Collections;
@@ -104,6 +105,11 @@ public class ClientManager {
                 sessionsPersister.onClientRemoved(realm, client);
             }
 
+            AuthenticationSessionProvider authSessions = realmManager.getSession().authenticationSessions();
+            if (authSessions != null) {
+                authSessions.onClientRemoved(realm, client);
+            }
+
             UserModel serviceAccountUser = realmManager.getSession().users().getServiceAccount(client);
             if (serviceAccountUser != null) {
                 new UserManager(realmManager.getSession()).removeUser(realm, serviceAccountUser);
diff --git a/services/src/main/java/org/keycloak/services/managers/ClientSessionCode.java b/services/src/main/java/org/keycloak/services/managers/ClientSessionCode.java
index ce7a8a1..59158e6 100755
--- a/services/src/main/java/org/keycloak/services/managers/ClientSessionCode.java
+++ b/services/src/main/java/org/keycloak/services/managers/ClientSessionCode.java
@@ -112,11 +112,6 @@ public class ClientSessionCode<CLIENT_SESSION extends CommonClientSessionModel> 
         CommonClientSessionModel clientSessionn = CodeGenerateUtil.getParser(sessionClass).parseSession(code, session, realm);;
         CLIENT_SESSION clientSession = sessionClass.cast(clientSessionn);
 
-        // TODO:mposolda Move this to somewhere else? Maybe LoginActionsService.sessionCodeChecks should be somehow even for non-action URLs...
-        if (clientSession != null) {
-            session.getContext().setClient(clientSession.getClient());
-        }
-
         return clientSession;
     }
 
@@ -168,8 +163,12 @@ public class ClientSessionCode<CLIENT_SESSION extends CommonClientSessionModel> 
 
 
     public Set<RoleModel> getRequestedRoles() {
+        return getRequestedRoles(commonLoginSession, realm);
+    }
+
+    public static Set<RoleModel> getRequestedRoles(CommonClientSessionModel clientSession, RealmModel realm) {
         Set<RoleModel> requestedRoles = new HashSet<>();
-        for (String roleId : commonLoginSession.getRoles()) {
+        for (String roleId : clientSession.getRoles()) {
             RoleModel role = realm.getRoleById(roleId);
             if (role != null) {
                 requestedRoles.add(role);
diff --git a/services/src/main/java/org/keycloak/services/managers/CodeGenerateUtil.java b/services/src/main/java/org/keycloak/services/managers/CodeGenerateUtil.java
index eac0e64..a975aa5 100644
--- a/services/src/main/java/org/keycloak/services/managers/CodeGenerateUtil.java
+++ b/services/src/main/java/org/keycloak/services/managers/CodeGenerateUtil.java
@@ -23,7 +23,6 @@ import java.util.Map;
 import org.jboss.logging.Logger;
 import org.keycloak.OAuth2Constants;
 import org.keycloak.models.AuthenticatedClientSessionModel;
-import org.keycloak.models.ClientSessionModel;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.UserSessionModel;
@@ -41,7 +40,6 @@ class CodeGenerateUtil {
     private static final Map<Class<? extends CommonClientSessionModel>, ClientSessionParser> PARSERS = new HashMap<>();
 
     static {
-        PARSERS.put(ClientSessionModel.class, new ClientSessionModelParser());
         PARSERS.put(AuthenticationSessionModel.class, new AuthenticationSessionModelParser());
         PARSERS.put(AuthenticatedClientSessionModel.class, new AuthenticatedClientSessionModelParser());
     }
@@ -78,54 +76,6 @@ class CodeGenerateUtil {
     // IMPLEMENTATIONS
 
 
-    // TODO: remove
-    private static class ClientSessionModelParser implements ClientSessionParser<ClientSessionModel> {
-
-
-        @Override
-        public ClientSessionModel parseSession(String code, KeycloakSession session, RealmModel realm) {
-            try {
-                String[] parts = code.split("\\.");
-                String id = parts[2];
-                return session.sessions().getClientSession(realm, id);
-            } catch (ArrayIndexOutOfBoundsException e) {
-                return null;
-            }
-        }
-
-        @Override
-        public String generateCode(ClientSessionModel clientSession, String actionId) {
-            StringBuilder sb = new StringBuilder();
-            sb.append("cls.");
-            sb.append(actionId);
-            sb.append('.');
-            sb.append(clientSession.getId());
-
-            return sb.toString();
-        }
-
-        @Override
-        public void removeExpiredSession(KeycloakSession session, ClientSessionModel clientSession) {
-            session.sessions().removeClientSession(clientSession.getRealm(), clientSession);
-        }
-
-        @Override
-        public String getNote(ClientSessionModel clientSession, String name) {
-            return clientSession.getNote(name);
-        }
-
-        @Override
-        public void removeNote(ClientSessionModel clientSession, String name) {
-            clientSession.removeNote(name);
-        }
-
-        @Override
-        public void setNote(ClientSessionModel clientSession, String name, String value) {
-            clientSession.setNote(name, value);
-        }
-    }
-
-
     private static class AuthenticationSessionModelParser implements ClientSessionParser<AuthenticationSessionModel> {
 
         @Override
@@ -193,20 +143,6 @@ class CodeGenerateUtil {
             sb.append('.');
             sb.append(clientUUID);
 
-            // TODO:mposolda codeChallengeMethod is not used anywhere. Not sure if it's bug of PKCE contribution. Doublecheck the PKCE specification what should be done regarding code
-            // https://tools.ietf.org/html/rfc7636#section-4
-            String codeChallenge = clientSession.getNote(OAuth2Constants.CODE_CHALLENGE);
-            String codeChallengeMethod = clientSession.getNote(OAuth2Constants.CODE_CHALLENGE_METHOD);
-            if (codeChallenge != null) {
-                logger.debugf("PKCE received codeChallenge = %s", codeChallenge);
-                if (codeChallengeMethod == null) {
-                    logger.debug("PKCE not received codeChallengeMethod, treating plain");
-                    codeChallengeMethod = OAuth2Constants.PKCE_METHOD_PLAIN;
-                } else {
-                    logger.debugf("PKCE received codeChallengeMethod = %s", codeChallengeMethod);
-                }
-            }
-
             return sb.toString();
         }
 
diff --git a/services/src/main/java/org/keycloak/services/managers/RealmManager.java b/services/src/main/java/org/keycloak/services/managers/RealmManager.java
index 3306bcd..e94ff3c 100755
--- a/services/src/main/java/org/keycloak/services/managers/RealmManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/RealmManager.java
@@ -47,6 +47,7 @@ import org.keycloak.representations.idm.OAuthClientRepresentation;
 import org.keycloak.representations.idm.RealmEventsConfigRepresentation;
 import org.keycloak.representations.idm.RealmRepresentation;
 import org.keycloak.representations.idm.RoleRepresentation;
+import org.keycloak.sessions.AuthenticationSessionProvider;
 import org.keycloak.storage.UserStorageProviderModel;
 import org.keycloak.services.clientregistration.policy.DefaultClientRegistrationPolicies;
 
@@ -248,6 +249,11 @@ public class RealmManager {
                 sessionsPersister.onRealmRemoved(realm);
             }
 
+            AuthenticationSessionProvider authSessions = session.authenticationSessions();
+            if (authSessions != null) {
+                authSessions.onRealmRemoved(realm);
+            }
+
           // Refresh periodic sync tasks for configured storageProviders
             List<UserStorageProviderModel> storageProviders = realm.getUserStorageProviders();
             UserStorageSyncManager storageSync = new UserStorageSyncManager();
diff --git a/services/src/main/java/org/keycloak/services/managers/UserSessionManager.java b/services/src/main/java/org/keycloak/services/managers/UserSessionManager.java
index 4528575..f347d4c 100644
--- a/services/src/main/java/org/keycloak/services/managers/UserSessionManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/UserSessionManager.java
@@ -104,8 +104,8 @@ public class UserSessionManager {
                             user.getUsername(), client.getClientId());
                 }
 
-                userSession.getAuthenticatedClientSessions().remove(client.getClientId());
-                persister.removeClientSession(clientSession.getId(), true);
+                clientSession.setUserSession(null);
+                persister.removeClientSession(userSession.getId(), client.getId(), true);
                 checkOfflineUserSessionHasClientSessions(realm, user, userSession);
                 anyRemoved = true;
             }
@@ -148,9 +148,8 @@ public class UserSessionManager {
                     clientSession.getId(), offlineUserSession.getId(), user.getUsername(), clientSession.getClient().getClientId());
         }
 
-        AuthenticatedClientSessionModel offlineClientSession = kcSession.sessions().createOfflineClientSession(clientSession);
-        offlineUserSession.getAuthenticatedClientSessions().put(clientSession.getClient().getId(), offlineClientSession);
-        persister.createClientSession(offlineUserSession, clientSession, true);
+        kcSession.sessions().createOfflineClientSession(clientSession, offlineUserSession);
+        persister.createClientSession(clientSession, true);
     }
 
     // Check if userSession has any offline clientSessions attached to it. Remove userSession if not
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ClientResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ClientResource.java
index 89b0a33..a92729e 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/ClientResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/ClientResource.java
@@ -25,8 +25,8 @@ import org.keycloak.common.Profile;
 import org.keycloak.common.util.Time;
 import org.keycloak.events.admin.OperationType;
 import org.keycloak.events.admin.ResourceType;
+import org.keycloak.models.AuthenticatedClientSessionModel;
 import org.keycloak.models.ClientModel;
-import org.keycloak.models.ClientSessionModel;
 import org.keycloak.models.Constants;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.ModelDuplicateException;
@@ -492,8 +492,11 @@ public class ClientResource {
             UserSessionRepresentation rep = ModelToRepresentation.toRepresentation(userSession);
 
             // Update lastSessionRefresh with the timestamp from clientSession
-            for (ClientSessionModel clientSession : userSession.getClientSessions()) {
-                if (client.getId().equals(clientSession.getClient().getId())) {
+            for (Map.Entry<String, AuthenticatedClientSessionModel> csEntry : userSession.getAuthenticatedClientSessions().entrySet()) {
+                String clientUuid = csEntry.getKey();
+                AuthenticatedClientSessionModel clientSession = csEntry.getValue();
+
+                if (client.getId().equals(clientUuid)) {
                     rep.setLastAccess(Time.toMillis(clientSession.getTimestamp()));
                     break;
                 }
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 ded189d..1810ed0 100755
--- a/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java
+++ b/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java
@@ -75,6 +75,7 @@ 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.BrowserHistoryHelper;
 import org.keycloak.services.util.CacheControlUtil;
 import org.keycloak.services.validation.Validation;
 import org.keycloak.sessions.AuthenticationSessionModel;
@@ -210,6 +211,8 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
             throw new ErrorPageException(session, Messages.INVALID_REQUEST);
         }
 
+        event.detail(Details.REDIRECT_URI, redirectUri);
+
         if (nonce == null || hash == null) {
             event.error(Errors.INVALID_REDIRECT_URI);
             throw new ErrorPageException(session, Messages.INVALID_REQUEST);
@@ -239,7 +242,10 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
             return Response.status(302).location(builder.build()).build();
         }
 
-
+        cookieResult.getSession();
+        event.session(cookieResult.getSession());
+        event.user(cookieResult.getUser());
+        event.detail(Details.USERNAME, cookieResult.getUser().getUsername());
 
         AuthenticatedClientSessionModel clientSession = null;
         for (AuthenticatedClientSessionModel cs : cookieResult.getSession().getAuthenticatedClientSessions().values()) {
@@ -264,7 +270,7 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
             throw new ErrorPageException(session, Messages.INVALID_REQUEST);
         }
 
-
+        event.detail(Details.IDENTITY_PROVIDER, providerId);
 
         ClientModel accountService = this.realmModel.getClientByClientId(Constants.ACCOUNT_MANAGEMENT_CLIENT_ID);
         if (!accountService.getId().equals(client.getId())) {
@@ -307,6 +313,7 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
         authSession.setClientNote(OIDCLoginProtocol.STATE_PARAM, UUID.randomUUID().toString());
         authSession.setAuthNote(LINKING_IDENTITY_PROVIDER, cookieResult.getSession().getId() + clientId + providerId);
 
+        event.detail(Details.CODE_ID, userSession.getId());
         event.success();
 
         try {
@@ -508,6 +515,7 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
 
         this.event.event(EventType.IDENTITY_PROVIDER_LOGIN)
                 .detail(Details.REDIRECT_URI, authenticationSession.getRedirectUri())
+                .detail(Details.IDENTITY_PROVIDER, providerId)
                 .detail(Details.IDENTITY_PROVIDER_USERNAME, context.getUsername());
 
         UserModel federatedUser = this.session.users().getUserByFederatedIdentity(federatedIdentityModel, this.realmModel);
@@ -786,6 +794,9 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
         authSession.setUserSessionNote(Details.IDENTITY_PROVIDER, providerId);
         authSession.setUserSessionNote(Details.IDENTITY_PROVIDER_USERNAME, context.getUsername());
 
+        event.detail(Details.IDENTITY_PROVIDER, providerId)
+                .detail(Details.IDENTITY_PROVIDER_USERNAME, context.getUsername());
+
         if (isDebugEnabled()) {
             logger.debugf("Performing local authentication for user [%s].", federatedUser);
         }
@@ -961,43 +972,39 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
     }
 
     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 (code == null) {
+            logger.debugf("Invalid request. Authorization code was null");
+            Response staleCodeError = redirectToErrorPage(Messages.INVALID_REQUEST);
+            return ParsedCodeContext.response(staleCodeError);
+        }
+
+        SessionCodeChecks checks = new SessionCodeChecks(realmModel, uriInfo, clientConnection, session, event, code, null, LoginActionsService.AUTHENTICATE_PATH);
+        checks.initialVerify();
+        if (!checks.verifyActiveAndValidAction(AuthenticationSessionModel.Action.AUTHENTICATE.name(), ClientSessionCode.ActionType.LOGIN)) {
+
+            AuthenticationSessionModel authSession = checks.getAuthenticationSession();
+            if (authSession != null) {
+                // Check if error happened during login or during linking from account management
+                Response accountManagementFailedLinking = checkAccountManagementFailedLinking(authSession, Messages.STALE_CODE_ACCOUNT);
+                if (accountManagementFailedLinking != null) {
+                    return ParsedCodeContext.response(accountManagementFailedLinking);
+                } else {
+                    Response errorResponse = checks.getResponse();
 
-                if (isDebugEnabled()) {
-                    logger.debugf("Authorization code is valid.");
+                    // Remove "code" from browser history
+                    errorResponse = BrowserHistoryHelper.getInstance().saveResponseAndRedirect(session, authSession, errorResponse, true);
+                    return ParsedCodeContext.response(errorResponse);
                 }
-
-                return ParsedCodeContext.clientSessionCode(clientCode);
+            } else {
+                return ParsedCodeContext.response(checks.getResponse());
+            }
+        } else {
+            if (isDebugEnabled()) {
+                logger.debugf("Authorization code is valid.");
             }
-        }
 
-        // 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);
+            return ParsedCodeContext.clientSessionCode(checks.getClientCode());
+        }
     }
 
     /**
@@ -1022,6 +1029,7 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
         }
 
         SamlService samlService = new SamlService(realmModel, event);
+        ResteasyProviderFactory.getInstance().injectProperties(samlService);
         AuthenticationSessionModel authSession = samlService.getOrCreateLoginSessionForIdpInitiatedSso(session, realmModel, oClient.get(), null);
 
         return ParsedCodeContext.clientSessionCode(new ClientSessionCode<>(session, this.realmModel, authSession));
@@ -1091,16 +1099,6 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
         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);
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 0338964..5dcb621 100755
--- a/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java
+++ b/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java
@@ -28,22 +28,20 @@ import org.keycloak.authentication.RequiredActionFactory;
 import org.keycloak.authentication.RequiredActionProvider;
 import org.keycloak.TokenVerifier;
 import org.keycloak.authentication.actiontoken.*;
+import org.keycloak.authentication.actiontoken.resetcred.ResetCredentialsActionTokenHandler;
 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.authentication.requiredactions.VerifyEmail;
 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;
 import org.keycloak.events.EventType;
 import org.keycloak.exceptions.TokenNotActiveException;
-import org.keycloak.forms.login.LoginFormsProvider;
 import org.keycloak.models.AuthenticationFlowModel;
 import org.keycloak.models.AuthenticatedClientSessionModel;
 import org.keycloak.models.ClientModel;
@@ -55,13 +53,10 @@ import org.keycloak.models.RealmModel;
 import org.keycloak.models.RoleModel;
 import org.keycloak.models.UserConsentModel;
 import org.keycloak.models.UserModel;
-import org.keycloak.models.UserModel.RequiredAction;
-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;
 import org.keycloak.protocol.oidc.OIDCLoginProtocol;
 import org.keycloak.protocol.oidc.utils.OIDCResponseMode;
 import org.keycloak.protocol.oidc.utils.OIDCResponseType;
@@ -74,10 +69,9 @@ import org.keycloak.services.managers.ClientSessionCode;
 import org.keycloak.services.messages.Messages;
 import org.keycloak.services.resources.LoginActionsServiceChecks.RestartFlowException;
 import org.keycloak.services.util.CacheControlUtil;
-import org.keycloak.services.util.PageExpiredRedirect;
+import org.keycloak.services.util.AuthenticationFlowURLHelper;
 import org.keycloak.services.util.BrowserHistoryHelper;
 import org.keycloak.sessions.AuthenticationSessionModel;
-import org.keycloak.sessions.CommonClientSessionModel;
 
 import javax.ws.rs.Consumes;
 import javax.ws.rs.GET;
@@ -94,6 +88,8 @@ import javax.ws.rs.core.UriBuilder;
 import javax.ws.rs.core.UriInfo;
 import javax.ws.rs.ext.Providers;
 import java.net.URI;
+import java.util.Map;
+
 import javax.ws.rs.core.*;
 import static org.keycloak.authentication.actiontoken.DefaultActionToken.ACTION_TOKEN_BASIC_CHECKS;
 
@@ -185,333 +181,14 @@ public class LoginActionsService {
     }
 
     private SessionCodeChecks checksForCode(String code, String execution, String flowPath) {
-        SessionCodeChecks res = new SessionCodeChecks(code, execution, flowPath);
-        res.initialVerify();
-        return res;
-    }
-
-    private SessionCodeChecks checksForCodeRefreshNotAllowed(String code, String execution, String flowPath) {
-        SessionCodeChecks res = new SessionCodeChecks(code, execution, flowPath);
-        res.setAllowRefresh(false);
+        SessionCodeChecks res = new SessionCodeChecks(realm, uriInfo, clientConnection, session, event, code, execution, flowPath);
         res.initialVerify();
         return res;
     }
 
 
-
-    private class SessionCodeChecks {
-        ClientSessionCode<AuthenticationSessionModel> clientCode;
-        Response response;
-        ClientSessionCode.ParseResult<AuthenticationSessionModel> result;
-        private boolean actionRequest;
-        private boolean allowRefresh = true;
-
-        private final String code;
-        private final String execution;
-        private final String flowPath;
-
-        public SessionCodeChecks(String code, String execution, String flowPath) {
-            this.code = code;
-            this.execution = execution;
-            this.flowPath = flowPath;
-        }
-
-        public AuthenticationSessionModel getAuthenticationSession() {
-            return clientCode == null ? null : clientCode.getClientSession();
-        }
-
-        public boolean passed() {
-            return response == null;
-        }
-
-        public boolean failed() {
-            return response != null;
-        }
-
-        public boolean isAllowRefresh() {
-            return allowRefresh;
-        }
-
-        public void setAllowRefresh(boolean allowRefresh) {
-            this.allowRefresh = allowRefresh;
-        }
-
-
-        boolean verifyCode(String expectedAction, ClientSessionCode.ActionType actionType) {
-            if (failed()) {
-                return false;
-            }
-
-            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. Redirecting to requiredActions now.");
-                    response = redirectToRequiredActions(null);
-                    return false;
-                } 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 true;
-        }
-
-
-        private boolean isActionActive(ClientSessionCode.ActionType actionType) {
-            if (!clientCode.isActionActive(actionType)) {
-                event.client(getAuthenticationSession().getClient());
-                event.clone().error(Errors.EXPIRED_CODE);
-
-                AuthenticationSessionModel authSession = getAuthenticationSession();
-                AuthenticationProcessor.resetFlow(authSession, AUTHENTICATE_PATH);
-                response = processAuthentication(false, null, authSession, Messages.LOGIN_TIMEOUT);
-                return false;
-            }
-            return true;
-        }
-
-
-        private AuthenticationSessionModel initialVerifyAuthSession() {
-            // Basic realm checks
-            if (!checkSsl()) {
-                event.error(Errors.SSL_REQUIRED);
-                response = ErrorPage.error(session, Messages.HTTPS_REQUIRED);
-                return null;
-            }
-            if (!realm.isEnabled()) {
-                event.error(Errors.REALM_DISABLED);
-                response = ErrorPage.error(session, Messages.REALM_NOT_ENABLED);
-                return null;
-            }
-
-            // object 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) {
-                return false;
-            }
-
-            // Check cached response from previous action request
-            response = BrowserHistoryHelper.getInstance().loadSavedResponse(session, authSession);
-            if (response != null) {
-                return false;
-            }
-
-            // Client checks
-            event.detail(Details.CODE_ID, authSession.getId());
-            ClientModel client = authSession.getClient();
-            if (client == null) {
-                event.error(Errors.CLIENT_NOT_FOUND);
-                response = ErrorPage.error(session, Messages.UNKNOWN_LOGIN_REQUESTER);
-                clientCode.removeExpiredClientSession();
-                return false;
-            }
-            if (!client.isEnabled()) {
-                event.error(Errors.CLIENT_DISABLED);
-                response = ErrorPage.error(session, Messages.LOGIN_REQUESTER_NOT_ENABLED);
-                clientCode.removeExpiredClientSession();
-                return false;
-            }
-            session.getContext().setClient(client);
-
-
-            // Check if it's action or not
-            if (code == null) {
-                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(flowPath, lastFlow)) {
-                        authSession.setAuthNote(AuthenticationProcessor.CURRENT_FLOW_PATH, flowPath);
-                        authSession.removeAuthNote(AuthenticationProcessor.CURRENT_AUTHENTICATION_EXECUTION);
-                        lastExecFromSession = null;
-                    }
-                }
-
-                if (ObjectUtil.isEqualOrBothNull(execution, lastExecFromSession)) {
-                    // Allow refresh of previous page
-                    clientCode = new ClientSessionCode<>(session, realm, authSession);
-                    actionRequest = false;
-                    return true;
-                } else {
-                    response = showPageExpired(authSession);
-                    return false;
-                }
-            } else {
-                result = ClientSessionCode.parseResult(code, session, realm, AuthenticationSessionModel.class);
-                clientCode = result.getCode();
-                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 (allowRefresh && 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(authSession);
-                    }
-                    return false;
-                }
-
-
-                actionRequest = execution != null;
-                authSession.setAuthNote(AuthenticationProcessor.LAST_PROCESSED_EXECUTION, execution);
-                return true;
-            }
-        }
-
-
-        public boolean verifyRequiredAction(String executedAction) {
-            if (failed()) {
-                return false;
-            }
-
-            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);
-
-                response = showPageExpired(getAuthenticationSession());
-
-                return false;
-            }
-
-            if (!isActionActive(ClientSessionCode.ActionType.USER)) return false;
-
-            final AuthenticationSessionModel authSession = getAuthenticationSession();
-
-            if (actionRequest) {
-                String currentRequiredAction = authSession.getAuthNote(AuthenticationProcessor.CURRENT_AUTHENTICATION_EXECUTION);
-                if (executedAction == null || !executedAction.equals(currentRequiredAction)) {
-                    logger.debug("required action doesn't match current required action");
-                    response = redirectToRequiredActions(currentRequiredAction);
-                    return false;
-                }
-            }
-            return true;
-        }
-    }
-
-
-    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);
-        } catch (Exception e) {
-            ServicesLogger.LOGGER.failedToParseRestartLoginCookie(e);
-        }
-
-        if (authSession != null) {
-
-            event.clone();
-            event.detail(Details.RESTART_AFTER_TIMEOUT, "true");
-            event.error(Errors.EXPIRED_CODE);
-
-            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;
-            }
-
-            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);
-        }
-    }
-
-
-    protected Response showPageExpired(AuthenticationSessionModel authSession) {
-        return new PageExpiredRedirect(session, realm, uriInfo)
-                .showPageExpired(authSession);
-    }
-
-
     protected URI getLastExecutionUrl(String flowPath, String executionId) {
-        return new PageExpiredRedirect(session, realm, uriInfo)
+        return new AuthenticationFlowURLHelper(session, realm, uriInfo)
                 .getLastExecutionUrl(flowPath, executionId);
     }
 
@@ -525,11 +202,11 @@ public class LoginActionsService {
     @GET
     public Response restartSession() {
         event.event(EventType.RESTART_AUTHENTICATION);
-        SessionCodeChecks checks = new SessionCodeChecks(null, null, null);
+        SessionCodeChecks checks = new SessionCodeChecks(realm, uriInfo, clientConnection, session, event, null, null, null);
 
         AuthenticationSessionModel authSession = checks.initialVerifyAuthSession();
         if (authSession == null) {
-            return checks.response;
+            return checks.getResponse();
         }
 
         String flowPath = authSession.getClientNote(AuthorizationEndpointBase.APP_INITIATED_FLOW);
@@ -539,11 +216,8 @@ public class LoginActionsService {
 
         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);
+        logger.debugf("Flow restart requested. Redirecting to %s", redirectUri);
         return Response.status(Response.Status.FOUND).location(redirectUri).build();
     }
 
@@ -561,12 +235,12 @@ public class LoginActionsService {
         event.event(EventType.LOGIN);
 
         SessionCodeChecks checks = checksForCode(code, execution, AUTHENTICATE_PATH);
-        if (!checks.verifyCode(ClientSessionModel.Action.AUTHENTICATE.name(), ClientSessionCode.ActionType.LOGIN)) {
-            return checks.response;
+        if (!checks.verifyActiveAndValidAction(ClientSessionModel.Action.AUTHENTICATE.name(), ClientSessionCode.ActionType.LOGIN)) {
+            return checks.getResponse();
         }
 
         AuthenticationSessionModel authSession = checks.getAuthenticationSession();
-        boolean actionRequest = checks.actionRequest;
+        boolean actionRequest = checks.isActionRequest();
 
         return processAuthentication(actionRequest, execution, authSession, null);
     }
@@ -605,6 +279,9 @@ public class LoginActionsService {
             } else {
                 response = processor.authenticate();
             }
+        } catch (WebApplicationException e) {
+            response = e.getResponse();
+            authSession = processor.getAuthenticationSession();
         } catch (Exception e) {
             response = processor.handleBrowserException(e);
             authSession = processor.getAuthenticationSession(); // Could be changed (eg. Forked flow)
@@ -696,30 +373,26 @@ public class LoginActionsService {
      */
     protected Response resetCredentials(String code, String execution) {
         SessionCodeChecks checks = checksForCode(code, execution, RESET_CREDENTIALS_PATH);
-        if (!checks.verifyCode(ClientSessionModel.Action.AUTHENTICATE.name(), ClientSessionCode.ActionType.USER)) {
-            return checks.response;
+        if (!checks.verifyActiveAndValidAction(ClientSessionModel.Action.AUTHENTICATE.name(), ClientSessionCode.ActionType.USER)) {
+            return checks.getResponse();
         }
         final AuthenticationSessionModel authSession = checks.getAuthenticationSession();
 
         if (!realm.isResetPasswordAllowed()) {
-            if (authSession != null) {
-                event.client(authSession.getClient());
-            }
             event.error(Errors.NOT_ALLOWED);
             return ErrorPage.error(session, Messages.RESET_CREDENTIAL_NOT_ALLOWED);
 
         }
 
-        return processResetCredentials(checks.actionRequest, execution, authSession);
+        return processResetCredentials(checks.isActionRequest(), execution, authSession);
     }
 
     /**
      * Handles a given token using the given token handler. If there is any {@link VerificationException} thrown
      * in the handler, it is handled automatically here to reduce boilerplate code.
      *
-     * @param tokenString Original token string
-     * @param eventError
-     * @param defaultErrorMessage
+     * @param key
+     * @param execution
      * @return
      */
     @Path("action-token")
@@ -888,30 +561,7 @@ public class LoginActionsService {
     }
 
     protected Response processResetCredentials(boolean actionRequest, String execution, AuthenticationSessionModel authSession) {
-        AuthenticationProcessor authProcessor = new AuthenticationProcessor() {
-
-            @Override
-            protected Response authenticationComplete() {
-                boolean firstBrokerLoginInProgress = (authenticationSession.getAuthNote(AbstractIdpAuthenticator.BROKERED_CONTEXT_NOTE) != null);
-                if (firstBrokerLoginInProgress) {
-
-                    UserModel linkingUser = AbstractIdpAuthenticator.getExistingUser(session, realm, authenticationSession);
-                    if (!linkingUser.getId().equals(authenticationSession.getAuthenticatedUser().getId())) {
-                        return ErrorPage.error(session, Messages.IDENTITY_PROVIDER_DIFFERENT_USER_MESSAGE, authenticationSession.getAuthenticatedUser().getUsername(), linkingUser.getUsername());
-                    }
-
-                    SerializedBrokeredIdentityContext serializedCtx = SerializedBrokeredIdentityContext.readFromAuthenticationSession(authSession, AbstractIdpAuthenticator.BROKERED_CONTEXT_NOTE);
-                    authSession.setAuthNote(AbstractIdpAuthenticator.FIRST_BROKER_LOGIN_SUCCESS, serializedCtx.getIdentityProviderId());
-
-                    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();
-                }
-            }
-        };
+        AuthenticationProcessor authProcessor = new ResetCredentialsActionTokenHandler.ResetCredsAuthenticationProcessor();
 
         return processFlow(actionRequest, execution, authSession, RESET_CREDENTIALS_PATH, realm.getResetCredentialsFlow(), null, authProcessor);
     }
@@ -958,19 +608,15 @@ public class LoginActionsService {
         }
 
         SessionCodeChecks checks = checksForCode(code, execution, REGISTRATION_PATH);
-        if (!checks.verifyCode(ClientSessionModel.Action.AUTHENTICATE.name(), ClientSessionCode.ActionType.LOGIN)) {
-            return checks.response;
+        if (!checks.verifyActiveAndValidAction(ClientSessionModel.Action.AUTHENTICATE.name(), ClientSessionCode.ActionType.LOGIN)) {
+            return checks.getResponse();
         }
 
-        ClientSessionCode<AuthenticationSessionModel> clientSessionCode = checks.clientCode;
-        AuthenticationSessionModel clientSession = clientSessionCode.getClientSession();
+        AuthenticationSessionModel authSession = checks.getAuthenticationSession();
 
-        // TODO:mposolda any consequences to do this for POST request too?
-        if (!isPostRequest) {
-            AuthenticationManager.expireIdentityCookie(realm, uriInfo, clientConnection);
-        }
+        AuthenticationManager.expireIdentityCookie(realm, uriInfo, clientConnection);
 
-        return processRegistration(checks.actionRequest, execution, clientSession, null);
+        return processRegistration(checks.isActionRequest(), execution, authSession, null);
     }
 
 
@@ -1010,8 +656,8 @@ public class LoginActionsService {
         event.event(eventType);
 
         SessionCodeChecks checks = checksForCode(code, execution, flowPath);
-        if (!checks.verifyCode(ClientSessionModel.Action.AUTHENTICATE.name(), ClientSessionCode.ActionType.LOGIN)) {
-            return checks.response;
+        if (!checks.verifyActiveAndValidAction(ClientSessionModel.Action.AUTHENTICATE.name(), ClientSessionCode.ActionType.LOGIN)) {
+            return checks.getResponse();
         }
         event.detail(Details.CODE_ID, code);
         final AuthenticationSessionModel authSession = checks.getAuthenticationSession();
@@ -1056,10 +702,14 @@ public class LoginActionsService {
 
         };
 
-        return processFlow(checks.actionRequest, execution, authSession, flowPath, brokerLoginFlow, null, processor);
+        return processFlow(checks.isActionRequest(), execution, authSession, flowPath, brokerLoginFlow, null, processor);
     }
 
     private Response redirectToAfterBrokerLoginEndpoint(AuthenticationSessionModel authSession, boolean firstBrokerLogin) {
+        return redirectToAfterBrokerLoginEndpoint(session, realm, uriInfo, authSession, firstBrokerLogin);
+    }
+
+    public static Response redirectToAfterBrokerLoginEndpoint(KeycloakSession session, RealmModel realm, UriInfo uriInfo, AuthenticationSessionModel authSession, boolean firstBrokerLogin) {
         ClientSessionCode<AuthenticationSessionModel> accessCode = new ClientSessionCode<>(session, realm, authSession);
         authSession.setTimestamp(Time.currentTime());
 
@@ -1085,11 +735,10 @@ public class LoginActionsService {
         String code = formData.getFirst("code");
         SessionCodeChecks checks = checksForCode(code, null, REQUIRED_ACTION);
         if (!checks.verifyRequiredAction(ClientSessionModel.Action.OAUTH_GRANT.name())) {
-            return checks.response;
+            return checks.getResponse();
         }
 
-        ClientSessionCode<AuthenticationSessionModel> accessCode = checks.clientCode;
-        AuthenticationSessionModel authSession = accessCode.getClientSession();
+        AuthenticationSessionModel authSession = checks.getAuthenticationSession();
 
         initLoginEvent(authSession);
 
@@ -1113,10 +762,10 @@ public class LoginActionsService {
             grantedConsent = new UserConsentModel(client);
             session.users().addConsent(realm, user.getId(), grantedConsent);
         }
-        for (RoleModel role : accessCode.getRequestedRoles()) {
+        for (RoleModel role : ClientSessionCode.getRequestedRoles(authSession, realm)) {
             grantedConsent.addGrantedRole(role);
         }
-        for (ProtocolMapperModel protocolMapper : accessCode.getRequestedProtocolMappers()) {
+        for (ProtocolMapperModel protocolMapper : ClientSessionCode.getRequestedProtocolMappers(authSession.getProtocolMappers(), client)) {
             if (protocolMapper.isConsentRequired() && protocolMapper.getConsentText() != null) {
                 grantedConsent.addGrantedProtocolMapper(protocolMapper);
             }
@@ -1130,23 +779,6 @@ public class LoginActionsService {
         return AuthenticationManager.redirectAfterSuccessfulFlow(session, realm, clientSession.getUserSession(), clientSession, request, uriInfo, clientConnection, event, authSession.getProtocol());
     }
 
-    @Path("email-verification")
-    @GET
-    public Response emailVerification(@QueryParam("code") String code, @QueryParam("execution") String execution) {
-        event.event(EventType.SEND_VERIFY_EMAIL);
-
-        SessionCodeChecks checks = checksForCodeRefreshNotAllowed(code, execution, REQUIRED_ACTION);
-        if (!checks.verifyCode(ClientSessionModel.Action.REQUIRED_ACTIONS.name(), ClientSessionCode.ActionType.USER)) {
-            return checks.response;
-        }
-        ClientSessionCode accessCode = checks.clientCode;
-        AuthenticationSessionModel authSession = checks.getAuthenticationSession();
-        initLoginEvent(authSession);
-
-        event.clone().event(EventType.SEND_VERIFY_EMAIL).detail(Details.EMAIL, authSession.getAuthenticatedUser().getEmail()).success();
-
-        return VerifyEmail.sendVerifyEmail(session, accessCode.getCode(), authSession.getAuthenticatedUser(), authSession);
-    }
 
     /**
      * Initiated by admin, not the user on login
@@ -1210,11 +842,12 @@ public class LoginActionsService {
         }
         event.detail(Details.REMEMBER_ME, rememberMe);
 
-        // TODO:mposolda Fix if this is called at firstBroker or postBroker login
-        /*
-                .detail(Details.IDENTITY_PROVIDER, userSession.getNote(Details.IDENTITY_PROVIDER))
-                .detail(Details.IDENTITY_PROVIDER_USERNAME, userSession.getNote(Details.IDENTITY_PROVIDER_USERNAME));
-                */
+        Map<String, String> userSessionNotes = authSession.getUserSessionNotes();
+        String identityProvider = userSessionNotes.get(Details.IDENTITY_PROVIDER);
+        if (identityProvider != null) {
+            event.detail(Details.IDENTITY_PROVIDER, identityProvider)
+                    .detail(Details.IDENTITY_PROVIDER_USERNAME, userSessionNotes.get(Details.IDENTITY_PROVIDER_USERNAME));
+        }
     }
 
     @Path(REQUIRED_ACTION)
@@ -1236,11 +869,11 @@ public class LoginActionsService {
 
         SessionCodeChecks checks = checksForCode(code, action, REQUIRED_ACTION);
         if (!checks.verifyRequiredAction(action)) {
-            return checks.response;
+            return checks.getResponse();
         }
 
         AuthenticationSessionModel authSession = checks.getAuthenticationSession();
-        if (!checks.actionRequest) {
+        if (!checks.isActionRequest()) {
             initLoginEvent(authSession);
             event.event(EventType.CUSTOM_REQUIRED_ACTION);
             return AuthenticationManager.nextActionAfterAuthentication(session, authSession, clientConnection, request, uriInfo, event);
@@ -1268,7 +901,9 @@ public class LoginActionsService {
         Response response;
         provider.processAction(context);
 
-        authSession.setAuthNote(AuthenticationProcessor.LAST_PROCESSED_EXECUTION, action);
+        if (action != null) {
+            authSession.setAuthNote(AuthenticationProcessor.LAST_PROCESSED_EXECUTION, action);
+        }
 
         if (context.getStatus() == RequiredActionContext.Status.SUCCESS) {
             event.clone().success();
@@ -1298,15 +933,4 @@ public class LoginActionsService {
         return BrowserHistoryHelper.getInstance().saveResponseAndRedirect(session, authSession, response, true);
     }
 
-    private Response redirectToRequiredActions(String action) {
-        UriBuilder uriBuilder = LoginActionsService.loginActionsBaseUrl(uriInfo)
-                .path(LoginActionsService.REQUIRED_ACTION);
-
-        if (action != null) {
-            uriBuilder.queryParam("execution", action);
-        }
-        URI redirect = uriBuilder.build(realm.getName());
-        return Response.status(302).location(redirect).build();
-    }
-
 }
diff --git a/services/src/main/java/org/keycloak/services/resources/SessionCodeChecks.java b/services/src/main/java/org/keycloak/services/resources/SessionCodeChecks.java
new file mode 100644
index 0000000..978ad6f
--- /dev/null
+++ b/services/src/main/java/org/keycloak/services/resources/SessionCodeChecks.java
@@ -0,0 +1,388 @@
+/*
+ * 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.resources;
+
+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.common.ClientConnection;
+import org.keycloak.common.util.ObjectUtil;
+import org.keycloak.events.Details;
+import org.keycloak.events.Errors;
+import org.keycloak.events.EventBuilder;
+import org.keycloak.forms.login.LoginFormsProvider;
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.ClientSessionModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserSessionModel;
+import org.keycloak.protocol.AuthorizationEndpointBase;
+import org.keycloak.protocol.RestartLoginCookie;
+import org.keycloak.services.ErrorPage;
+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.messages.Messages;
+import org.keycloak.services.util.BrowserHistoryHelper;
+import org.keycloak.services.util.AuthenticationFlowURLHelper;
+import org.keycloak.sessions.AuthenticationSessionModel;
+
+
+public class SessionCodeChecks {
+
+    private static final Logger logger = Logger.getLogger(SessionCodeChecks.class);
+
+    private AuthenticationSessionModel authSession;
+    private ClientSessionCode<AuthenticationSessionModel> clientCode;
+    private Response response;
+    private boolean actionRequest;
+
+    private final RealmModel realm;
+    private final UriInfo uriInfo;
+    private final ClientConnection clientConnection;
+    private final KeycloakSession session;
+    private final EventBuilder event;
+
+    private final String code;
+    private final String execution;
+    private final String flowPath;
+
+
+    public SessionCodeChecks(RealmModel realm, UriInfo uriInfo, ClientConnection clientConnection, KeycloakSession session, EventBuilder event, String code, String execution, String flowPath) {
+        this.realm = realm;
+        this.uriInfo = uriInfo;
+        this.clientConnection = clientConnection;
+        this.session = session;
+        this.event = event;
+
+        this.code = code;
+        this.execution = execution;
+        this.flowPath = flowPath;
+    }
+
+
+    public AuthenticationSessionModel getAuthenticationSession() {
+        return authSession;
+    }
+
+
+    private boolean failed() {
+        return response != null;
+    }
+
+
+    public Response getResponse() {
+        return response;
+    }
+
+
+    public ClientSessionCode<AuthenticationSessionModel> getClientCode() {
+        return clientCode;
+    }
+
+    public boolean isActionRequest() {
+        return actionRequest;
+    }
+
+
+    private boolean checkSsl() {
+        if (uriInfo.getBaseUri().getScheme().equals("https")) {
+            return true;
+        } else {
+            return !realm.getSslRequired().isRequired(clientConnection);
+        }
+    }
+
+
+    public AuthenticationSessionModel initialVerifyAuthSession() {
+        // Basic realm checks
+        if (!checkSsl()) {
+            event.error(Errors.SSL_REQUIRED);
+            response = ErrorPage.error(session, Messages.HTTPS_REQUIRED);
+            return null;
+        }
+        if (!realm.isEnabled()) {
+            event.error(Errors.REALM_DISABLED);
+            response = ErrorPage.error(session, Messages.REALM_NOT_ENABLED);
+            return null;
+        }
+
+        // object 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;
+    }
+
+
+    public boolean initialVerify() {
+        // Basic realm checks and authenticationSession retrieve
+        authSession = initialVerifyAuthSession();
+        if (authSession == null) {
+            return false;
+        }
+
+        // Check cached response from previous action request
+        response = BrowserHistoryHelper.getInstance().loadSavedResponse(session, authSession);
+        if (response != null) {
+            return false;
+        }
+
+        // Client checks
+        event.detail(Details.CODE_ID, authSession.getId());
+        ClientModel client = authSession.getClient();
+        if (client == null) {
+            event.error(Errors.CLIENT_NOT_FOUND);
+            response = ErrorPage.error(session, Messages.UNKNOWN_LOGIN_REQUESTER);
+            clientCode.removeExpiredClientSession();
+            return false;
+        }
+
+        event.client(client);
+        session.getContext().setClient(client);
+
+        if (!client.isEnabled()) {
+            event.error(Errors.CLIENT_DISABLED);
+            response = ErrorPage.error(session, Messages.LOGIN_REQUESTER_NOT_ENABLED);
+            clientCode.removeExpiredClientSession();
+            return false;
+        }
+
+
+        // Check if it's action or not
+        if (code == null) {
+            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.debugf("Transition between flows! Current flow: %s, Previous flow: %s", flowPath, lastFlow);
+
+                // Don't allow moving to different flow if I am on requiredActions already
+                if (ClientSessionModel.Action.AUTHENTICATE.name().equals(authSession.getAction())) {
+                    authSession.setAuthNote(AuthenticationProcessor.CURRENT_FLOW_PATH, flowPath);
+                    authSession.removeAuthNote(AuthenticationProcessor.CURRENT_AUTHENTICATION_EXECUTION);
+                    lastExecFromSession = null;
+                }
+            }
+
+            if (ObjectUtil.isEqualOrBothNull(execution, lastExecFromSession)) {
+                // Allow refresh of previous page
+                clientCode = new ClientSessionCode<>(session, realm, authSession);
+                actionRequest = false;
+                return true;
+            } else {
+                response = showPageExpired(authSession);
+                return false;
+            }
+        } else {
+            ClientSessionCode.ParseResult<AuthenticationSessionModel> result = ClientSessionCode.parseResult(code, session, realm, AuthenticationSessionModel.class);
+            clientCode = result.getCode();
+            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.CURRENT_AUTHENTICATION_EXECUTION))) {
+                    String latestFlowPath = authSession.getAuthNote(AuthenticationProcessor.CURRENT_FLOW_PATH);
+                    URI redirectUri = getLastExecutionUrl(latestFlowPath, execution);
+
+                    logger.debugf("Invalid action code, but execution matches. So just redirecting to %s", redirectUri);
+                    authSession.setAuthNote(LoginActionsService.FORWARDED_ERROR_MESSAGE_NOTE, Messages.EXPIRED_ACTION);
+                    response = Response.status(Response.Status.FOUND).location(redirectUri).build();
+                } else {
+                    response = showPageExpired(authSession);
+                }
+                return false;
+            }
+
+
+            actionRequest = true;
+            if (execution != null) {
+                authSession.setAuthNote(AuthenticationProcessor.LAST_PROCESSED_EXECUTION, execution);
+            }
+            return true;
+        }
+    }
+
+
+    public boolean verifyActiveAndValidAction(String expectedAction, ClientSessionCode.ActionType actionType) {
+        if (failed()) {
+            return false;
+        }
+
+        if (!isActionActive(actionType)) {
+            return false;
+        }
+
+        if (!clientCode.isValidAction(expectedAction)) {
+            AuthenticationSessionModel authSession = getAuthenticationSession();
+            if (ClientSessionModel.Action.REQUIRED_ACTIONS.name().equals(authSession.getAction())) {
+                logger.debugf("Incorrect action '%s' . User authenticated already.", authSession.getAction());
+                response = showPageExpired(authSession);
+                return false;
+            } else {
+                logger.errorf("Bad action. Expected action '%s', current action '%s'", expectedAction, authSession.getAction());
+                response = ErrorPage.error(session, Messages.EXPIRED_CODE);
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+
+    private boolean isActionActive(ClientSessionCode.ActionType actionType) {
+        if (!clientCode.isActionActive(actionType)) {
+            event.clone().error(Errors.EXPIRED_CODE);
+
+            AuthenticationProcessor.resetFlow(authSession, LoginActionsService.AUTHENTICATE_PATH);
+
+            authSession.setAuthNote(LoginActionsService.FORWARDED_ERROR_MESSAGE_NOTE, Messages.LOGIN_TIMEOUT);
+
+            URI redirectUri = getLastExecutionUrl(LoginActionsService.AUTHENTICATE_PATH, null);
+            logger.debugf("Flow restart after timeout. Redirecting to %s", redirectUri);
+            response = Response.status(Response.Status.FOUND).location(redirectUri).build();
+            return false;
+        }
+        return true;
+    }
+
+
+    public boolean verifyRequiredAction(String executedAction) {
+        if (failed()) {
+            return false;
+        }
+
+        if (!clientCode.isValidAction(ClientSessionModel.Action.REQUIRED_ACTIONS.name())) {
+            logger.debugf("Expected required action, but session action is '%s' . Showing expired page now.", authSession.getAction());
+            event.error(Errors.INVALID_CODE);
+
+            response = showPageExpired(authSession);
+
+            return false;
+        }
+
+        if (!isActionActive(ClientSessionCode.ActionType.USER)) {
+            return false;
+        }
+
+        if (actionRequest) {
+            String currentRequiredAction = authSession.getAuthNote(AuthenticationProcessor.CURRENT_AUTHENTICATION_EXECUTION);
+            if (executedAction == null || !executedAction.equals(currentRequiredAction)) {
+                logger.debug("required action doesn't match current required action");
+                response = redirectToRequiredActions(currentRequiredAction);
+                return false;
+            }
+        }
+        return true;
+    }
+
+
+    private Response restartAuthenticationSessionFromCookie() {
+        logger.debug("Authentication session not found. Trying to restart from cookie.");
+        AuthenticationSessionModel authSession = null;
+        try {
+            authSession = RestartLoginCookie.restartSession(session, realm);
+        } catch (Exception e) {
+            ServicesLogger.LOGGER.failedToParseRestartLoginCookie(e);
+        }
+
+        if (authSession != null) {
+
+            event.clone();
+            event.detail(Details.RESTART_AFTER_TIMEOUT, "true");
+            event.error(Errors.EXPIRED_CODE);
+
+            String warningMessage = Messages.LOGIN_TIMEOUT;
+            authSession.setAuthNote(LoginActionsService.FORWARDED_ERROR_MESSAGE_NOTE, warningMessage);
+
+            String flowPath = authSession.getClientNote(AuthorizationEndpointBase.APP_INITIATED_FLOW);
+            if (flowPath == null) {
+                flowPath = LoginActionsService.AUTHENTICATE_PATH;
+            }
+
+            URI redirectUri = getLastExecutionUrl(flowPath, null);
+            logger.debugf("Authentication session restart from cookie succeeded. Redirecting to %s", redirectUri);
+            return Response.status(Response.Status.FOUND).location(redirectUri).build();
+        } else {
+            // Finally need to show error as all the fallbacks failed
+            event.error(Errors.INVALID_CODE);
+            return ErrorPage.error(session, Messages.INVALID_CODE);
+        }
+    }
+
+
+    private Response redirectToRequiredActions(String action) {
+        UriBuilder uriBuilder = LoginActionsService.loginActionsBaseUrl(uriInfo)
+                .path(LoginActionsService.REQUIRED_ACTION);
+
+        if (action != null) {
+            uriBuilder.queryParam("execution", action);
+        }
+        URI redirect = uriBuilder.build(realm.getName());
+        return Response.status(302).location(redirect).build();
+    }
+
+
+    private URI getLastExecutionUrl(String flowPath, String executionId) {
+        return new AuthenticationFlowURLHelper(session, realm, uriInfo)
+                .getLastExecutionUrl(flowPath, executionId);
+    }
+
+
+    private Response showPageExpired(AuthenticationSessionModel authSession) {
+        return new AuthenticationFlowURLHelper(session, realm, uriInfo)
+                .showPageExpired(authSession);
+    }
+
+}
diff --git a/services/src/main/java/org/keycloak/services/scheduled/ClearExpiredUserSessions.java b/services/src/main/java/org/keycloak/services/scheduled/ClearExpiredUserSessions.java
index 5935eb0..5315be4 100755
--- a/services/src/main/java/org/keycloak/services/scheduled/ClearExpiredUserSessions.java
+++ b/services/src/main/java/org/keycloak/services/scheduled/ClearExpiredUserSessions.java
@@ -32,6 +32,7 @@ public class ClearExpiredUserSessions implements ScheduledTask {
         UserSessionProvider sessions = session.sessions();
         for (RealmModel realm : session.realms().getRealms()) {
             sessions.removeExpired(realm);
+            session.authenticationSessions().removeExpired(realm);
         }
     }
 
diff --git a/services/src/main/java/org/keycloak/services/util/BrowserHistoryHelper.java b/services/src/main/java/org/keycloak/services/util/BrowserHistoryHelper.java
index 89cda2c..890d21c 100644
--- a/services/src/main/java/org/keycloak/services/util/BrowserHistoryHelper.java
+++ b/services/src/main/java/org/keycloak/services/util/BrowserHistoryHelper.java
@@ -77,7 +77,7 @@ public abstract class BrowserHistoryHelper {
                 if (entity instanceof String) {
                     String responseString = (String) entity;
 
-                    URI lastExecutionURL = new PageExpiredRedirect(session, session.getContext().getRealm(), session.getContext().getUri()).getLastExecutionUrl(authSession);
+                    URI lastExecutionURL = new AuthenticationFlowURLHelper(session, session.getContext().getRealm(), session.getContext().getUri()).getLastExecutionUrl(authSession);
 
                     // Inject javascript for history "replaceState"
                     String responseWithJavascript = responseWithJavascript(responseString, lastExecutionURL.toString());
@@ -124,6 +124,7 @@ public abstract class BrowserHistoryHelper {
     }
 
 
+    // This impl is limited ATM. Saved request doesn't save response HTTP headers, so they may not be fully restored..
     private static class RedirectAfterPostHelper extends BrowserHistoryHelper {
 
         private static final String CACHED_RESPONSE = "cached.response";
@@ -141,10 +142,11 @@ public abstract class BrowserHistoryHelper {
                     String responseString = (String) entity;
                     authSession.setAuthNote(CACHED_RESPONSE, responseString);
 
-                    URI lastExecutionURL = new PageExpiredRedirect(session, session.getContext().getRealm(), session.getContext().getUri()).getLastExecutionUrl(authSession);
+                    URI lastExecutionURL = new AuthenticationFlowURLHelper(session, session.getContext().getRealm(), session.getContext().getUri()).getLastExecutionUrl(authSession);
 
-                    // TODO:mposolda trace
-                    logger.infof("Saved response challenge and redirect to %s", lastExecutionURL);
+                    if (logger.isTraceEnabled()) {
+                        logger.tracef("Saved response challenge and redirect to %s", lastExecutionURL);
+                    }
 
                     return Response.status(302).location(lastExecutionURL).build();
                 }
@@ -160,11 +162,12 @@ public abstract class BrowserHistoryHelper {
             if (savedResponse != null) {
                 authSession.removeAuthNote(CACHED_RESPONSE);
 
-                // TODO:mposolda trace
-                logger.infof("Restored previously saved request");
+                if (logger.isTraceEnabled()) {
+                    logger.tracef("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.
+                BrowserSecurityHeaderSetup.headers(builder, session.getContext().getRealm()); // TODO rather all the headers from the saved response should be added here.
                 return builder.build();
             }
 
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 a8c42b1..00be27c 100755
--- a/services/src/main/java/org/keycloak/social/twitter/TwitterIdentityProvider.java
+++ b/services/src/main/java/org/keycloak/social/twitter/TwitterIdentityProvider.java
@@ -119,6 +119,8 @@ public class TwitterIdentityProvider extends AbstractIdentityProvider<OAuth2Iden
                 return callback.cancelled(state);
             }
 
+            Response errorResponse = null;
+
             try {
                 Twitter twitter = new TwitterFactory().getInstance();
 
@@ -155,17 +157,21 @@ public class TwitterIdentityProvider extends AbstractIdentityProvider<OAuth2Iden
 
                 return callback.authenticated(identity);
             } catch (WebApplicationException e) {
+                sendErrorEvent();
                 return e.getResponse();
             } catch (Exception e) {
                 logger.error("Could get user profile from twitter.", e);
+                sendErrorEvent();
+                return ErrorPage.error(session, Messages.UNEXPECTED_ERROR_HANDLING_RESPONSE);
             }
+        }
+
+        private void sendErrorEvent() {
             EventBuilder event = new EventBuilder(realm, session, clientConnection);
             event.event(EventType.LOGIN);
             event.error("twitter_login_failed");
-            return ErrorPage.error(session, Messages.UNEXPECTED_ERROR_HANDLING_RESPONSE);
         }
 
-
     }
 
     @Override
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractFirstBrokerLoginTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractFirstBrokerLoginTest.java
index 474417c..f2dffaf 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractFirstBrokerLoginTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractFirstBrokerLoginTest.java
@@ -33,12 +33,16 @@ import org.keycloak.representations.idm.IdentityProviderRepresentation;
 import org.keycloak.services.managers.RealmManager;
 import org.keycloak.testsuite.pages.IdpConfirmLinkPage;
 import org.keycloak.testsuite.pages.IdpLinkEmailPage;
+import org.keycloak.testsuite.pages.InfoPage;
+import org.keycloak.testsuite.pages.LoginExpiredPage;
 import org.keycloak.testsuite.pages.LoginPasswordUpdatePage;
 import org.keycloak.testsuite.pages.LoginUpdateProfileEditUsernameAllowedPage;
 import org.keycloak.testsuite.rule.KeycloakRule;
 import org.keycloak.testsuite.rule.WebResource;
+import org.keycloak.testsuite.rule.WebRule;
 import org.openqa.selenium.By;
 import org.openqa.selenium.NoSuchElementException;
+import org.openqa.selenium.WebDriver;
 import org.openqa.selenium.WebElement;
 
 import javax.mail.internet.MimeMessage;
@@ -71,6 +75,9 @@ public abstract class AbstractFirstBrokerLoginTest extends AbstractIdentityProvi
     @WebResource
     protected LoginPasswordUpdatePage passwordUpdatePage;
 
+    @WebResource
+    protected LoginExpiredPage loginExpiredPage;
+
 
 
     /**
@@ -361,6 +368,101 @@ public abstract class AbstractFirstBrokerLoginTest extends AbstractIdentityProvi
 
 
     /**
+     * Variation of previous test, which uses browser buttons (back, refresh etc)
+     */
+    @Test
+    public void testLinkAccountByReauthenticationWithPassword_browserButtons() throws Exception {
+        // Remove smtp config. The reauthentication by username+password screen will be automatically used then
+        final Map<String, String> smtpConfig = new HashMap<>();
+        brokerServerRule.update(new KeycloakRule.KeycloakSetup() {
+
+            @Override
+            public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel realmWithBroker) {
+                setUpdateProfileFirstLogin(realmWithBroker, IdentityProviderRepresentation.UPFLM_OFF);
+                smtpConfig.putAll(realmWithBroker.getSmtpConfig());
+                realmWithBroker.setSmtpConfig(Collections.<String, String>emptyMap());
+            }
+
+        }, APP_REALM_ID);
+
+
+        // Use invalid username for the first time
+        loginIDP("foo");
+        assertTrue(driver.getCurrentUrl().startsWith("http://localhost:8082/auth/"));
+        this.loginPage.login("pedroigor", "password");
+
+
+        this.idpConfirmLinkPage.assertCurrent();
+        Assert.assertEquals("User with email psilva@redhat.com already exists. How do you want to continue?", this.idpConfirmLinkPage.getMessage());
+
+        // Click browser 'back' and then 'forward' and then continue
+        driver.navigate().back();
+        Assert.assertTrue(driver.getPageSource().contains("You are already logged in."));
+        driver.navigate().forward();
+        this.loginExpiredPage.assertCurrent();
+        this.loginExpiredPage.clickLoginContinueLink();
+        this.idpConfirmLinkPage.assertCurrent();
+
+        // Click browser 'back' on review profile page
+        this.idpConfirmLinkPage.clickReviewProfile();
+        this.updateProfilePage.assertCurrent();
+        driver.navigate().back();
+        this.loginExpiredPage.assertCurrent();
+        this.loginExpiredPage.clickLoginContinueLink();
+        this.updateProfilePage.assertCurrent();
+        this.updateProfilePage.update("Pedro", "Igor", "psilva@redhat.com");
+
+        this.idpConfirmLinkPage.assertCurrent();
+        this.idpConfirmLinkPage.clickLinkAccount();
+
+        // Login screen shown. Username is prefilled and disabled. Registration link and social buttons are not shown
+        Assert.assertEquals("Log in to " + APP_REALM_ID, this.driver.getTitle());
+        Assert.assertEquals("pedroigor", this.loginPage.getUsername());
+        Assert.assertFalse(this.loginPage.isUsernameInputEnabled());
+
+        Assert.assertEquals("Authenticate as pedroigor to link your account with " + getProviderId(), this.loginPage.getInfoMessage());
+
+        try {
+            this.loginPage.findSocialButton(getProviderId());
+            Assert.fail("Not expected to see social button with " + getProviderId());
+        } catch (NoSuchElementException expected) {
+        }
+
+        try {
+            this.loginPage.clickRegister();
+            Assert.fail("Not expected to see register link");
+        } catch (NoSuchElementException expected) {
+        }
+
+        // Use bad password first
+        this.loginPage.login("password1");
+        Assert.assertEquals("Invalid username or password.", this.loginPage.getError());
+
+        // Click browser 'back' and then continue
+        this.driver.navigate().back();
+        this.loginExpiredPage.assertCurrent();
+        this.loginExpiredPage.clickLoginContinueLink();
+
+        // Use correct password now
+        this.loginPage.login("password");
+
+        // authenticated and redirected to app. User is linked with identity provider
+        assertFederatedUser("pedroigor", "psilva@redhat.com", "pedroigor");
+
+
+        // Restore smtp config
+        brokerServerRule.update(new KeycloakRule.KeycloakSetup() {
+
+            @Override
+            public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel realmWithBroker) {
+                realmWithBroker.setSmtpConfig(smtpConfig);
+            }
+
+        }, APP_REALM_ID);
+    }
+
+
+    /**
      * Tests that duplication is detected and user wants to link federatedIdentity with existing account. He will confirm link by reauthentication (confirm password on login screen)
      * and additionally he goes through "forget password"
      */
@@ -418,6 +520,93 @@ public abstract class AbstractFirstBrokerLoginTest extends AbstractIdentityProvi
     }
 
 
+    /**
+     * Same like above, but "forget password" link is opened in different browser
+     */
+    @Test
+    public void testLinkAccountByReauthentication_forgetPassword_differentBrowser() throws Throwable {
+        brokerServerRule.update(new KeycloakRule.KeycloakSetup() {
+
+            @Override
+            public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel realmWithBroker) {
+                setExecutionRequirement(realmWithBroker, DefaultAuthenticationFlows.FIRST_BROKER_LOGIN_HANDLE_EXISTING_SUBFLOW,
+                        IdpEmailVerificationAuthenticatorFactory.PROVIDER_ID, AuthenticationExecutionModel.Requirement.DISABLED);
+
+                setUpdateProfileFirstLogin(realmWithBroker, IdentityProviderRepresentation.UPFLM_OFF);
+            }
+
+        }, APP_REALM_ID);
+
+        loginIDP("pedroigor");
+
+        this.idpConfirmLinkPage.assertCurrent();
+        Assert.assertEquals("User with email psilva@redhat.com already exists. How do you want to continue?", this.idpConfirmLinkPage.getMessage());
+        this.idpConfirmLinkPage.clickLinkAccount();
+
+        // Click "Forget password" on login page. Email sent directly because username is known
+        Assert.assertEquals("Log in to " + APP_REALM_ID, this.driver.getTitle());
+        this.loginPage.resetPassword();
+
+        Assert.assertEquals("Log in to " + APP_REALM_ID, this.driver.getTitle());
+        Assert.assertEquals("You should receive an email shortly with further instructions.", this.loginPage.getSuccessMessage());
+
+        // Click on link from email
+        Assert.assertEquals(1, greenMail.getReceivedMessages().length);
+        MimeMessage message = greenMail.getReceivedMessages()[0];
+        String linkFromMail = getVerificationEmailLink(message);
+
+        // Simulate 2nd browser
+        WebRule webRule2 = new WebRule(this);
+        webRule2.before();
+
+        WebDriver driver2 = webRule2.getDriver();
+        LoginPasswordUpdatePage passwordUpdatePage2 = webRule2.getPage(LoginPasswordUpdatePage.class);
+        InfoPage infoPage2 = webRule2.getPage(InfoPage.class);
+
+        driver2.navigate().to(linkFromMail.trim());
+
+        // Need to update password now
+        passwordUpdatePage2.assertCurrent();
+        passwordUpdatePage2.changePassword("password", "password");
+
+        // authenticated, but not redirected to app. Just seeing info page.
+        infoPage2.assertCurrent();
+        Assert.assertEquals("Your account has been updated.", infoPage2.getInfo());
+
+        // User is not yet linked with identity provider. He needs to authenticate again in 1st browser
+        RealmModel realmWithBroker = getRealm();
+        Set<FederatedIdentityModel> federatedIdentities = this.session.users().getFederatedIdentities(this.session.users().getUserByUsername("pedroigor", realmWithBroker), realmWithBroker);
+        assertEquals(0, federatedIdentities.size());
+
+        // Continue with 1st browser
+        loginIDP("pedroigor");
+
+        this.idpConfirmLinkPage.assertCurrent();
+        Assert.assertEquals("User with email psilva@redhat.com already exists. How do you want to continue?", this.idpConfirmLinkPage.getMessage());
+        this.idpConfirmLinkPage.clickLinkAccount();
+
+        Assert.assertEquals("Log in to " + APP_REALM_ID, this.driver.getTitle());
+        this.loginPage.login("password");
+
+        // authenticated and redirected to app. User is linked with identity provider
+        assertFederatedUser("pedroigor", "psilva@redhat.com", "pedroigor");
+
+        // Revert everything
+        webRule2.after();
+
+        brokerServerRule.update(new KeycloakRule.KeycloakSetup() {
+
+            @Override
+            public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel realmWithBroker) {
+                setExecutionRequirement(realmWithBroker, DefaultAuthenticationFlows.FIRST_BROKER_LOGIN_HANDLE_EXISTING_SUBFLOW,
+                        IdpEmailVerificationAuthenticatorFactory.PROVIDER_ID, AuthenticationExecutionModel.Requirement.ALTERNATIVE);
+
+            }
+
+        }, APP_REALM_ID);
+    }
+
+
     protected void assertFederatedUser(String expectedUsername, String expectedEmail, String expectedFederatedUsername) {
         assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/test-app"));
         UserModel federatedUser = getFederatedUser();
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 14c98dd..781714a 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
@@ -74,8 +74,6 @@ public abstract class AbstractKeycloakIdentityProviderTest extends AbstractIdent
         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");
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 b576cbf..b2ecd41 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
@@ -117,11 +117,9 @@ public class OIDCKeycloakServerBrokerWithConsentTest extends AbstractIdentityPro
             grantPage.assertCurrent();
             grantPage.cancel();
 
-            // Assert error page with backToApplication link displayed
-            errorPage.assertCurrent();
-            errorPage.clickBackToApplication();
-
-            assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/auth/realms/realm-with-broker/protocol/openid-connect/auth"));
+            // Assert login page with "You took too long to login..." message
+            assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/auth/realms/realm-with-broker/login-actions/authenticate"));
+            Assert.assertEquals("You took too long to login. Login process starting from beginning.", loginPage.getError());
 
         } finally {
             Time.setOffset(0);
@@ -152,14 +150,9 @@ public class OIDCKeycloakServerBrokerWithConsentTest extends AbstractIdentityPro
             grantPage.assertCurrent();
             grantPage.cancel();
 
-            // Assert error page without backToApplication link (clientSession expired and was removed on the server)
-            errorPage.assertCurrent();
-            try {
-                errorPage.clickBackToApplication();
-                fail("Not expected to have link backToApplication available");
-            } catch (NoSuchElementException nsee) {
-                // Expected;
-            }
+            // Assert login page with "You took too long to login..." message
+            assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/auth/realms/realm-with-broker/login-actions/authenticate"));
+            Assert.assertEquals("You took too long to login. Login process starting from beginning.", loginPage.getError());
 
         } finally {
             Time.setOffset(0);
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 8ee397a..db08e81 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
@@ -22,15 +22,21 @@ import org.junit.Assert;
 import org.junit.Before;
 import org.junit.ClassRule;
 import org.junit.Test;
+import org.keycloak.common.util.Time;
 import org.keycloak.models.ClientModel;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.UserManager;
 import org.keycloak.models.UserModel;
+import org.keycloak.services.managers.ClientManager;
+import org.keycloak.services.managers.RealmManager;
 import org.keycloak.sessions.AuthenticationSessionModel;
 import org.keycloak.sessions.CommonClientSessionModel;
 import org.keycloak.testsuite.rule.KeycloakRule;
 
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
 /**
  * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
  */
@@ -53,7 +59,6 @@ public class AuthenticationSessionProviderTest {
     @After
     public void after() {
         resetSession();
-        session.sessions().removeUserSessions(realm);
         UserModel user1 = session.users().getUserByUsername("user1", realm);
         UserModel user2 = session.users().getUserByUsername("user2", realm);
 
@@ -87,7 +92,7 @@ public class AuthenticationSessionProviderTest {
 
         // Ensure session is here
         authSession = session.authenticationSessions().getAuthenticationSession(realm, authSession.getId());
-        testLoginSession(authSession, client1.getId(), null, "foo");
+        testAuthenticationSession(authSession, client1.getId(), null, "foo");
         Assert.assertEquals(100, authSession.getTimestamp());
 
         // Update and commit
@@ -99,7 +104,7 @@ public class AuthenticationSessionProviderTest {
 
         // Ensure session was updated
         authSession = session.authenticationSessions().getAuthenticationSession(realm, authSession.getId());
-        testLoginSession(authSession, client1.getId(), user1.getId(), "foo-updated");
+        testAuthenticationSession(authSession, client1.getId(), user1.getId(), "foo-updated");
         Assert.assertEquals(200, authSession.getTimestamp());
 
         // Remove and commit
@@ -113,7 +118,7 @@ public class AuthenticationSessionProviderTest {
     }
 
     @Test
-    public void testLoginSessionRestart() {
+    public void testAuthenticationSessionRestart() {
         ClientModel client1 = realm.getClientByClientId("test-app");
         UserModel user1 = session.users().getUserByUsername("user1", realm);
 
@@ -136,7 +141,7 @@ public class AuthenticationSessionProviderTest {
         resetSession();
 
         authSession = session.authenticationSessions().getAuthenticationSession(realm, authSession.getId());
-        testLoginSession(authSession, client1.getId(), null, null);
+        testAuthenticationSession(authSession, client1.getId(), null, null);
         Assert.assertTrue(authSession.getTimestamp() > 0);
 
         Assert.assertTrue(authSession.getClientNotes().isEmpty());
@@ -145,7 +150,120 @@ public class AuthenticationSessionProviderTest {
 
     }
 
-    private void testLoginSession(AuthenticationSessionModel authSession, String expectedClientId, String expectedUserId, String expectedAction) {
+
+    @Test
+    public void testExpiredAuthSessions() {
+        try {
+            realm.setAccessCodeLifespan(10);
+            realm.setAccessCodeLifespanUserAction(10);
+            realm.setAccessCodeLifespanLogin(30);
+
+            // Login lifespan is largest
+            String authSessionId = session.authenticationSessions().createAuthenticationSession(realm, realm.getClientByClientId("test-app")).getId();
+            resetSession();
+
+            Time.setOffset(25);
+            session.authenticationSessions().removeExpired(realm);
+            resetSession();
+
+            assertNotNull(session.authenticationSessions().getAuthenticationSession(realm, authSessionId));
+
+            Time.setOffset(35);
+            session.authenticationSessions().removeExpired(realm);
+            resetSession();
+
+            assertNull(session.authenticationSessions().getAuthenticationSession(realm, authSessionId));
+
+            // User action is largest
+            realm.setAccessCodeLifespanUserAction(40);
+
+            Time.setOffset(0);
+            authSessionId = session.authenticationSessions().createAuthenticationSession(realm, realm.getClientByClientId("test-app")).getId();
+            resetSession();
+
+            Time.setOffset(35);
+            session.authenticationSessions().removeExpired(realm);
+            resetSession();
+
+            assertNotNull(session.authenticationSessions().getAuthenticationSession(realm, authSessionId));
+
+            Time.setOffset(45);
+            session.authenticationSessions().removeExpired(realm);
+            resetSession();
+
+            assertNull(session.authenticationSessions().getAuthenticationSession(realm, authSessionId));
+
+            // Access code is largest
+            realm.setAccessCodeLifespan(50);
+
+            Time.setOffset(0);
+            authSessionId = session.authenticationSessions().createAuthenticationSession(realm, realm.getClientByClientId("test-app")).getId();
+            resetSession();
+
+            Time.setOffset(45);
+            session.authenticationSessions().removeExpired(realm);
+            resetSession();
+
+            assertNotNull(session.authenticationSessions().getAuthenticationSession(realm, authSessionId));
+
+            Time.setOffset(55);
+            session.authenticationSessions().removeExpired(realm);
+            resetSession();
+
+            assertNull(session.authenticationSessions().getAuthenticationSession(realm, authSessionId));
+        } finally {
+            Time.setOffset(0);
+
+            realm.setAccessCodeLifespan(60);
+            realm.setAccessCodeLifespanUserAction(300);
+            realm.setAccessCodeLifespanLogin(1800);
+
+        }
+    }
+
+
+    @Test
+    public void testOnRealmRemoved() {
+        RealmModel fooRealm = session.realms().createRealm("foo-realm");
+        ClientModel fooClient = fooRealm.addClient("foo-client");
+
+        String authSessionId = session.authenticationSessions().createAuthenticationSession(realm, realm.getClientByClientId("test-app")).getId();
+        String authSessionId2 = session.authenticationSessions().createAuthenticationSession(fooRealm, fooClient).getId();
+
+        resetSession();
+
+        new RealmManager(session).removeRealm(session.realms().getRealmByName("foo-realm"));
+
+        resetSession();
+
+        AuthenticationSessionModel authSession = session.authenticationSessions().getAuthenticationSession(realm, authSessionId);
+        testAuthenticationSession(authSession, realm.getClientByClientId("test-app").getId(), null, null);
+        Assert.assertNull(session.authenticationSessions().getAuthenticationSession(realm, authSessionId2));
+    }
+
+    @Test
+    public void testOnClientRemoved() {
+        String authSessionId = session.authenticationSessions().createAuthenticationSession(realm, realm.getClientByClientId("test-app")).getId();
+        String authSessionId2 = session.authenticationSessions().createAuthenticationSession(realm, realm.getClientByClientId("third-party")).getId();
+
+        String testAppClientUUID = realm.getClientByClientId("test-app").getId();
+
+        resetSession();
+
+        new ClientManager(new RealmManager(session)).removeClient(realm, realm.getClientByClientId("third-party"));
+
+        resetSession();
+
+        AuthenticationSessionModel authSession = session.authenticationSessions().getAuthenticationSession(realm, authSessionId);
+        testAuthenticationSession(authSession, testAppClientUUID, null, null);
+        Assert.assertNull(session.authenticationSessions().getAuthenticationSession(realm, authSessionId2));
+
+        // Revert client
+        realm.addClient("third-party");
+    }
+
+
+    private void testAuthenticationSession(AuthenticationSessionModel authSession, String expectedClientId, String expectedUserId, String expectedAction) {
         Assert.assertEquals(expectedClientId, authSession.getClient().getId());
 
         if (expectedUserId == null) {
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserSessionInitializerTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserSessionInitializerTest.java
index bc71a09..8c01e2c 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserSessionInitializerTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserSessionInitializerTest.java
@@ -25,14 +25,15 @@ import org.junit.Test;
 import org.keycloak.cluster.ClusterProvider;
 import org.keycloak.common.util.Time;
 import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
+import org.keycloak.models.AuthenticatedClientSessionModel;
 import org.keycloak.models.ClientModel;
-import org.keycloak.models.ClientSessionModel;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.UserModel;
 import org.keycloak.models.UserSessionModel;
 import org.keycloak.models.UserSessionProvider;
 import org.keycloak.models.UserSessionProviderFactory;
+import org.keycloak.models.utils.KeycloakModelUtils;
 import org.keycloak.protocol.oidc.OIDCLoginProtocol;
 import org.keycloak.models.UserManager;
 import org.keycloak.services.managers.UserSessionManager;
@@ -47,129 +48,129 @@ import java.util.Set;
  */
 public class UserSessionInitializerTest {
 
-    // TODO:mposolda
-//    @ClassRule
-//    public static KeycloakRule kc = new KeycloakRule();
-//
-//    private KeycloakSession session;
-//    private RealmModel realm;
-//    private UserSessionManager sessionManager;
-//
-//    @Before
-//    public void before() {
-//        session = kc.startSession();
-//        realm = session.realms().getRealm("test");
-//        session.users().addUser(realm, "user1").setEmail("user1@localhost");
-//        session.users().addUser(realm, "user2").setEmail("user2@localhost");
-//        sessionManager = new UserSessionManager(session);
-//    }
-//
-//    @After
-//    public void after() {
-//        resetSession();
-//        session.sessions().removeUserSessions(realm);
-//        UserModel user1 = session.users().getUserByUsername("user1", realm);
-//        UserModel user2 = session.users().getUserByUsername("user2", realm);
-//
-//        UserManager um = new UserManager(session);
-//        um.removeUser(realm, user1);
-//        um.removeUser(realm, user2);
-//        kc.stopSession(session, true);
-//    }
-//
-//    @Test
-//    public void testUserSessionInitializer() {
-//        UserSessionModel[] origSessions = createSessions();
-//
-//        resetSession();
-//
-//        // Create and persist offline sessions
-//        int started = Time.currentTime();
-//        int serverStartTime = session.getProvider(ClusterProvider.class).getClusterStartupTime();
-//
-//        for (UserSessionModel origSession : origSessions) {
-//            UserSessionModel userSession = session.sessions().getUserSession(realm, origSession.getId());
-//            for (ClientSessionModel clientSession : userSession.getClientSessions()) {
-//                sessionManager.createOrUpdateOfflineSession(clientSession, userSession);
-//            }
-//        }
-//
-//        resetSession();
-//
-//        // Delete cache (persisted sessions are still kept)
-//        session.sessions().onRealmRemoved(realm);
-//
-//        // Clear ispn cache to ensure initializerState is removed as well
-//        InfinispanConnectionProvider infinispan = session.getProvider(InfinispanConnectionProvider.class);
-//        infinispan.getCache(InfinispanConnectionProvider.WORK_CACHE_NAME).clear();
-//
-//        resetSession();
-//
-//        ClientModel testApp = realm.getClientByClientId("test-app");
-//        ClientModel thirdparty = realm.getClientByClientId("third-party");
-//        Assert.assertEquals(0, session.sessions().getOfflineSessionsCount(realm, testApp));
-//        Assert.assertEquals(0, session.sessions().getOfflineSessionsCount(realm, thirdparty));
-//
-//        // Load sessions from persister into infinispan/memory
-//        UserSessionProviderFactory userSessionFactory = (UserSessionProviderFactory) session.getKeycloakSessionFactory().getProviderFactory(UserSessionProvider.class);
-//        userSessionFactory.loadPersistentSessions(session.getKeycloakSessionFactory(), 1, 2);
-//
-//        resetSession();
-//
-//        // Assert sessions are in
-//        testApp = realm.getClientByClientId("test-app");
-//        Assert.assertEquals(3, session.sessions().getOfflineSessionsCount(realm, testApp));
-//        Assert.assertEquals(1, session.sessions().getOfflineSessionsCount(realm, thirdparty));
-//
-//        List<UserSessionModel> loadedSessions = session.sessions().getOfflineUserSessions(realm, testApp, 0, 10);
-//        UserSessionProviderTest.assertSessions(loadedSessions, origSessions);
-//
-//        UserSessionPersisterProviderTest.assertSessionLoaded(loadedSessions, origSessions[0].getId(), session.users().getUserByUsername("user1", realm), "127.0.0.1", started, serverStartTime, "test-app", "third-party");
-//        UserSessionPersisterProviderTest.assertSessionLoaded(loadedSessions, origSessions[1].getId(), session.users().getUserByUsername("user1", realm), "127.0.0.2", started, serverStartTime, "test-app");
-//        UserSessionPersisterProviderTest.assertSessionLoaded(loadedSessions, origSessions[2].getId(), session.users().getUserByUsername("user2", realm), "127.0.0.3", started, serverStartTime, "test-app");
-//    }
-//
-//    private ClientSessionModel createClientSession(ClientModel client, UserSessionModel userSession, String redirect, String state, Set<String> roles, Set<String> protocolMappers) {
-//        ClientSessionModel clientSession = session.sessions().createClientSession(realm, client);
-//        if (userSession != null) clientSession.setUserSession(userSession);
-//        clientSession.setRedirectUri(redirect);
-//        if (state != null) clientSession.setNote(OIDCLoginProtocol.STATE_PARAM, state);
-//        if (roles != null) clientSession.setRoles(roles);
-//        if (protocolMappers != null) clientSession.setProtocolMappers(protocolMappers);
-//        return clientSession;
-//    }
-//
-//    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);
-//
-//        Set<String> roles = new HashSet<String>();
-//        roles.add("one");
-//        roles.add("two");
-//
-//        Set<String> protocolMappers = new HashSet<String>();
-//        protocolMappers.add("mapper-one");
-//        protocolMappers.add("mapper-two");
-//
-//        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);
-//        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);
-//        createClientSession(realm.getClientByClientId("test-app"), sessions[2], "http://redirect", "state", new HashSet<String>(), new HashSet<String>());
-//
-//        resetSession();
-//
-//        return sessions;
-//    }
-//
-//    private void resetSession() {
-//        kc.stopSession(session, true);
-//        session = kc.startSession();
-//        realm = session.realms().getRealm("test");
-//        sessionManager = new UserSessionManager(session);
-//    }
+
+    @ClassRule
+    public static KeycloakRule kc = new KeycloakRule();
+
+    private KeycloakSession session;
+    private RealmModel realm;
+    private UserSessionManager sessionManager;
+
+    @Before
+    public void before() {
+        session = kc.startSession();
+        realm = session.realms().getRealm("test");
+        session.users().addUser(realm, "user1").setEmail("user1@localhost");
+        session.users().addUser(realm, "user2").setEmail("user2@localhost");
+        sessionManager = new UserSessionManager(session);
+    }
+
+    @After
+    public void after() {
+        resetSession();
+        session.sessions().removeUserSessions(realm);
+        UserModel user1 = session.users().getUserByUsername("user1", realm);
+        UserModel user2 = session.users().getUserByUsername("user2", realm);
+
+        UserManager um = new UserManager(session);
+        um.removeUser(realm, user1);
+        um.removeUser(realm, user2);
+        kc.stopSession(session, true);
+    }
+
+    @Test
+    public void testUserSessionInitializer() {
+        UserSessionModel[] origSessions = createSessions();
+
+        resetSession();
+
+        // Create and persist offline sessions
+        int started = Time.currentTime();
+        int serverStartTime = session.getProvider(ClusterProvider.class).getClusterStartupTime();
+
+        for (UserSessionModel origSession : origSessions) {
+            UserSessionModel userSession = session.sessions().getUserSession(realm, origSession.getId());
+            for (AuthenticatedClientSessionModel clientSession : userSession.getAuthenticatedClientSessions().values()) {
+                sessionManager.createOrUpdateOfflineSession(clientSession, userSession);
+            }
+        }
+
+        resetSession();
+
+        // Delete cache (persisted sessions are still kept)
+        session.sessions().onRealmRemoved(realm);
+
+        // Clear ispn cache to ensure initializerState is removed as well
+        InfinispanConnectionProvider infinispan = session.getProvider(InfinispanConnectionProvider.class);
+        infinispan.getCache(InfinispanConnectionProvider.WORK_CACHE_NAME).clear();
+
+        resetSession();
+
+        ClientModel testApp = realm.getClientByClientId("test-app");
+        ClientModel thirdparty = realm.getClientByClientId("third-party");
+        Assert.assertEquals(0, session.sessions().getOfflineSessionsCount(realm, testApp));
+        Assert.assertEquals(0, session.sessions().getOfflineSessionsCount(realm, thirdparty));
+
+        // Load sessions from persister into infinispan/memory
+        UserSessionProviderFactory userSessionFactory = (UserSessionProviderFactory) session.getKeycloakSessionFactory().getProviderFactory(UserSessionProvider.class);
+        userSessionFactory.loadPersistentSessions(session.getKeycloakSessionFactory(), 1, 2);
+
+        resetSession();
+
+        // Assert sessions are in
+        testApp = realm.getClientByClientId("test-app");
+        Assert.assertEquals(3, session.sessions().getOfflineSessionsCount(realm, testApp));
+        Assert.assertEquals(1, session.sessions().getOfflineSessionsCount(realm, thirdparty));
+
+        List<UserSessionModel> loadedSessions = session.sessions().getOfflineUserSessions(realm, testApp, 0, 10);
+        UserSessionProviderTest.assertSessions(loadedSessions, origSessions);
+
+        UserSessionPersisterProviderTest.assertSessionLoaded(loadedSessions, origSessions[0].getId(), session.users().getUserByUsername("user1", realm), "127.0.0.1", started, serverStartTime, "test-app", "third-party");
+        UserSessionPersisterProviderTest.assertSessionLoaded(loadedSessions, origSessions[1].getId(), session.users().getUserByUsername("user1", realm), "127.0.0.2", started, serverStartTime, "test-app");
+        UserSessionPersisterProviderTest.assertSessionLoaded(loadedSessions, origSessions[2].getId(), session.users().getUserByUsername("user2", realm), "127.0.0.3", started, serverStartTime, "test-app");
+    }
+
+    private AuthenticatedClientSessionModel createClientSession(ClientModel client, UserSessionModel userSession, String redirect, String state, Set<String> roles, Set<String> protocolMappers) {
+        AuthenticatedClientSessionModel clientSession = session.sessions().createClientSession(realm, client, userSession);
+        if (userSession != null) clientSession.setUserSession(userSession);
+        clientSession.setRedirectUri(redirect);
+        if (state != null) clientSession.setNote(OIDCLoginProtocol.STATE_PARAM, state);
+        if (roles != null) clientSession.setRoles(roles);
+        if (protocolMappers != null) clientSession.setProtocolMappers(protocolMappers);
+        return clientSession;
+    }
+
+    private UserSessionModel[] createSessions() {
+        UserSessionModel[] sessions = new UserSessionModel[3];
+        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");
+        roles.add("two");
+
+        Set<String> protocolMappers = new HashSet<String>();
+        protocolMappers.add("mapper-one");
+        protocolMappers.add("mapper-two");
+
+        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(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(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();
+
+        return sessions;
+    }
+
+    private void resetSession() {
+        kc.stopSession(session, true);
+        session = kc.startSession();
+        realm = session.realms().getRealm("test");
+        sessionManager = new UserSessionManager(session);
+    }
 
 }
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserSessionPersisterProviderTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserSessionPersisterProviderTest.java
index 9e46ec6..8c89046 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserSessionPersisterProviderTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserSessionPersisterProviderTest.java
@@ -23,13 +23,14 @@ import org.junit.Before;
 import org.junit.ClassRule;
 import org.junit.Test;
 import org.keycloak.common.util.Time;
+import org.keycloak.models.AuthenticatedClientSessionModel;
 import org.keycloak.models.ClientModel;
-import org.keycloak.models.ClientSessionModel;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.UserModel;
 import org.keycloak.models.UserSessionModel;
 import org.keycloak.models.session.UserSessionPersisterProvider;
+import org.keycloak.models.utils.KeycloakModelUtils;
 import org.keycloak.protocol.oidc.OIDCLoginProtocol;
 import org.keycloak.services.managers.ClientManager;
 import org.keycloak.services.managers.RealmManager;
@@ -45,398 +46,398 @@ import java.util.Set;
  * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
  */
 public class UserSessionPersisterProviderTest {
-// TODO:mposolda
-
-//    @ClassRule
-//    public static KeycloakRule kc = new KeycloakRule();
-//
-//    private KeycloakSession session;
-//    private RealmModel realm;
-//    private UserSessionPersisterProvider persister;
-//
-//    @Before
-//    public void before() {
-//        session = kc.startSession();
-//        realm = session.realms().getRealm("test");
-//        session.users().addUser(realm, "user1").setEmail("user1@localhost");
-//        session.users().addUser(realm, "user2").setEmail("user2@localhost");
-//        persister = session.getProvider(UserSessionPersisterProvider.class);
-//    }
-//
-//    @After
-//    public void after() {
-//        resetSession();
-//        session.sessions().removeUserSessions(realm);
-//        UserModel user1 = session.users().getUserByUsername("user1", realm);
-//        UserModel user2 = session.users().getUserByUsername("user2", realm);
-//
-//        UserManager um = new UserManager(session);
-//        if (user1 != null) {
-//            um.removeUser(realm, user1);
-//        }
-//        if (user2 != null) {
-//            um.removeUser(realm, user2);
-//        }
-//        kc.stopSession(session, true);
-//    }
-//
-//    @Test
-//    public void testPersistenceWithLoad() {
-//        // Create some sessions in infinispan
-//        int started = Time.currentTime();
-//        UserSessionModel[] origSessions = createSessions();
-//
-//        resetSession();
-//
-//        // Persist 3 created userSessions and clientSessions as offline
-//        ClientModel testApp = realm.getClientByClientId("test-app");
-//        List<UserSessionModel> userSessions = session.sessions().getUserSessions(realm, testApp);
-//        for (UserSessionModel userSession : userSessions) {
-//            persistUserSession(userSession, true);
-//        }
-//
-//        // Persist 1 online session
-//        UserSessionModel userSession = session.sessions().getUserSession(realm, origSessions[0].getId());
-//        persistUserSession(userSession, false);
-//
-//        resetSession();
-//
-//        // Assert online session
-//        List<UserSessionModel> loadedSessions = loadPersistedSessionsPaginated(false, 1, 1, 1);
-//        UserSessionProviderTest.assertSession(loadedSessions.get(0), session.users().getUserByUsername("user1", realm), "127.0.0.1", started, started, "test-app", "third-party");
-//
-//        // Assert offline sessions
-//        loadedSessions = loadPersistedSessionsPaginated(true, 2, 2, 3);
-//        UserSessionProviderTest.assertSessions(loadedSessions, origSessions);
-//
-//        assertSessionLoaded(loadedSessions, origSessions[0].getId(), session.users().getUserByUsername("user1", realm), "127.0.0.1", started, started, "test-app", "third-party");
-//        assertSessionLoaded(loadedSessions, origSessions[1].getId(), session.users().getUserByUsername("user1", realm), "127.0.0.2", started, started, "test-app");
-//        assertSessionLoaded(loadedSessions, origSessions[2].getId(), session.users().getUserByUsername("user2", realm), "127.0.0.3", started, started, "test-app");
-//    }
-//
-//    @Test
-//    public void testUpdateTimestamps() {
-//        // Create some sessions in infinispan
-//        int started = Time.currentTime();
-//        UserSessionModel[] origSessions = createSessions();
-//
-//        resetSession();
-//
-//        // Persist 3 created userSessions and clientSessions as offline
-//        ClientModel testApp = realm.getClientByClientId("test-app");
-//        List<UserSessionModel> userSessions = session.sessions().getUserSessions(realm, testApp);
-//        for (UserSessionModel userSession : userSessions) {
-//            persistUserSession(userSession, true);
-//        }
-//
-//        // Persist 1 online session
-//        UserSessionModel userSession = session.sessions().getUserSession(realm, origSessions[0].getId());
-//        persistUserSession(userSession, false);
-//
-//        resetSession();
-//
-//        // update timestamps
-//        int newTime = started + 50;
-//        persister.updateAllTimestamps(newTime);
-//
-//        // Assert online session
-//        List<UserSessionModel> loadedSessions = loadPersistedSessionsPaginated(false, 1, 1, 1);
-//        Assert.assertEquals(2, assertTimestampsUpdated(loadedSessions, newTime));
-//
-//        // Assert offline sessions
-//        loadedSessions = loadPersistedSessionsPaginated(true, 2, 2, 3);
-//        Assert.assertEquals(4, assertTimestampsUpdated(loadedSessions, newTime));
-//    }
-//
-//    private int assertTimestampsUpdated(List<UserSessionModel> loadedSessions, int expectedTime) {
-//        int clientSessionsCount = 0;
-//        for (UserSessionModel loadedSession : loadedSessions) {
-//            Assert.assertEquals(expectedTime, loadedSession.getLastSessionRefresh());
-//            for (ClientSessionModel clientSession : loadedSession.getClientSessions()) {
-//                Assert.assertEquals(expectedTime, clientSession.getTimestamp());
-//                clientSessionsCount++;
-//            }
-//        }
-//        return clientSessionsCount;
-//    }
-//
-//    @Test
-//    public void testUpdateAndRemove() {
-//        // Create some sessions in infinispan
-//        int started = Time.currentTime();
-//        UserSessionModel[] origSessions = createSessions();
-//
-//        resetSession();
-//
-//        // Persist 1 offline session
-//        UserSessionModel userSession = session.sessions().getUserSession(realm, origSessions[1].getId());
-//        persistUserSession(userSession, true);
-//
-//        resetSession();
-//
-//        // Load offline session
-//        List<UserSessionModel> loadedSessions = loadPersistedSessionsPaginated(true, 10, 1, 1);
-//        UserSessionModel persistedSession = loadedSessions.get(0);
-//        UserSessionProviderTest.assertSession(persistedSession, session.users().getUserByUsername("user1", realm), "127.0.0.2", started, started, "test-app");
-//
-//        // Update userSession
-//        Time.setOffset(10);
-//        try {
-//            persistedSession.setLastSessionRefresh(Time.currentTime());
-//            persistedSession.setNote("foo", "bar");
-//            persistedSession.setState(UserSessionModel.State.LOGGING_IN);
-//            persister.updateUserSession(persistedSession, true);
-//
-//            // create new clientSession
-//            ClientSessionModel clientSession = createClientSession(realm.getClientByClientId("third-party"), session.sessions().getUserSession(realm, persistedSession.getId()),
-//                    "http://redirect", "state", new HashSet<String>(), new HashSet<String>());
-//            persister.createClientSession(clientSession, true);
-//
-//            resetSession();
-//
-//            // Assert session updated
-//            loadedSessions = loadPersistedSessionsPaginated(true, 10, 1, 1);
-//            persistedSession = loadedSessions.get(0);
-//            UserSessionProviderTest.assertSession(persistedSession, session.users().getUserByUsername("user1", realm), "127.0.0.2", started, started+10, "test-app", "third-party");
-//            Assert.assertEquals("bar", persistedSession.getNote("foo"));
-//            Assert.assertEquals(UserSessionModel.State.LOGGING_IN, persistedSession.getState());
-//
-//            // Remove clientSession
-//            persister.removeClientSession(clientSession.getId(), true);
-//
-//            resetSession();
-//
-//            // Assert clientSession removed
-//            loadedSessions = loadPersistedSessionsPaginated(true, 10, 1, 1);
-//            persistedSession = loadedSessions.get(0);
-//            UserSessionProviderTest.assertSession(persistedSession, session.users().getUserByUsername("user1", realm), "127.0.0.2", started, started + 10, "test-app");
-//
-//            // Remove userSession
-//            persister.removeUserSession(persistedSession.getId(), true);
-//
-//            resetSession();
-//
-//            // Assert nothing found
-//            loadPersistedSessionsPaginated(true, 10, 0, 0);
-//        } finally {
-//            Time.setOffset(0);
-//        }
-//    }
-//
-//    @Test
-//    public void testOnRealmRemoved() {
-//        RealmModel fooRealm = session.realms().createRealm("foo", "foo");
-//        fooRealm.addClient("foo-app");
-//        session.users().addUser(fooRealm, "user3");
-//
-//        UserSessionModel userSession = session.sessions().createUserSession(fooRealm, session.users().getUserByUsername("user3", fooRealm), "user3", "127.0.0.1", "form", true, null, null);
-//        createClientSession(fooRealm.getClientByClientId("foo-app"), userSession, "http://redirect", "state", new HashSet<String>(), new HashSet<String>());
-//
-//        resetSession();
-//
-//        // Persist offline session
-//        fooRealm = session.realms().getRealm("foo");
-//        userSession = session.sessions().getUserSession(fooRealm, userSession.getId());
-//        persistUserSession(userSession, true);
-//
-//        resetSession();
-//
-//        // Assert session was persisted
-//        loadPersistedSessionsPaginated(true, 10, 1, 1);
-//
-//        // Remove realm
-//        RealmManager realmMgr = new RealmManager(session);
-//        realmMgr.removeRealm(realmMgr.getRealm("foo"));
-//
-//        resetSession();
-//
-//        // Assert nothing loaded
-//        loadPersistedSessionsPaginated(true, 10, 0, 0);
-//    }
-//
-//    @Test
-//    public void testOnClientRemoved() {
-//        int started = Time.currentTime();
-//
-//        RealmModel fooRealm = session.realms().createRealm("foo", "foo");
-//        fooRealm.addClient("foo-app");
-//        fooRealm.addClient("bar-app");
-//        session.users().addUser(fooRealm, "user3");
-//
-//        UserSessionModel userSession = session.sessions().createUserSession(fooRealm, session.users().getUserByUsername("user3", fooRealm), "user3", "127.0.0.1", "form", true, null, null);
-//        createClientSession(fooRealm.getClientByClientId("foo-app"), userSession, "http://redirect", "state", new HashSet<String>(), new HashSet<String>());
-//        createClientSession(fooRealm.getClientByClientId("bar-app"), userSession, "http://redirect", "state", new HashSet<String>(), new HashSet<String>());
-//
-//        resetSession();
-//
-//        // Persist offline session
-//        fooRealm = session.realms().getRealm("foo");
-//        userSession = session.sessions().getUserSession(fooRealm, userSession.getId());
-//        persistUserSession(userSession, true);
-//
-//        resetSession();
-//
-//        RealmManager realmMgr = new RealmManager(session);
-//        ClientManager clientMgr = new ClientManager(realmMgr);
-//        fooRealm = realmMgr.getRealm("foo");
-//
-//        // Assert session was persisted with both clientSessions
-//        UserSessionModel persistedSession = loadPersistedSessionsPaginated(true, 10, 1, 1).get(0);
-//        UserSessionProviderTest.assertSession(persistedSession, session.users().getUserByUsername("user3", fooRealm), "127.0.0.1", started, started, "foo-app", "bar-app");
-//
-//        // Remove foo-app client
-//        ClientModel client = fooRealm.getClientByClientId("foo-app");
-//        clientMgr.removeClient(fooRealm, client);
-//
-//        resetSession();
-//
-//        realmMgr = new RealmManager(session);
-//        clientMgr = new ClientManager(realmMgr);
-//        fooRealm = realmMgr.getRealm("foo");
-//
-//        // Assert just one bar-app clientSession persisted now
-//        persistedSession = loadPersistedSessionsPaginated(true, 10, 1, 1).get(0);
-//        UserSessionProviderTest.assertSession(persistedSession, session.users().getUserByUsername("user3", fooRealm), "127.0.0.1", started, started, "bar-app");
-//
-//        // Remove bar-app client
-//        client = fooRealm.getClientByClientId("bar-app");
-//        clientMgr.removeClient(fooRealm, client);
-//
-//        resetSession();
-//
-//        // Assert nothing loaded - userSession was removed as well because it was last userSession
-//        loadPersistedSessionsPaginated(true, 10, 0, 0);
-//
-//        // Cleanup
-//        realmMgr = new RealmManager(session);
-//        realmMgr.removeRealm(realmMgr.getRealm("foo"));
-//    }
-//
-//    @Test
-//    public void testOnUserRemoved() {
-//        // Create some sessions in infinispan
-//        int started = Time.currentTime();
-//        UserSessionModel[] origSessions = createSessions();
-//
-//        resetSession();
-//
-//        // Persist 2 offline sessions of 2 users
-//        UserSessionModel userSession1 = session.sessions().getUserSession(realm, origSessions[1].getId());
-//        UserSessionModel userSession2 = session.sessions().getUserSession(realm, origSessions[2].getId());
-//        persistUserSession(userSession1, true);
-//        persistUserSession(userSession2, true);
-//
-//        resetSession();
-//
-//        // Load offline sessions
-//        List<UserSessionModel> loadedSessions = loadPersistedSessionsPaginated(true, 10, 1, 2);
-//
-//        // Properly delete user and assert his offlineSession removed
-//        UserModel user1 = session.users().getUserByUsername("user1", realm);
-//        new UserManager(session).removeUser(realm, user1);
-//
-//        resetSession();
-//
-//        Assert.assertEquals(1, persister.getUserSessionsCount(true));
-//        loadedSessions = loadPersistedSessionsPaginated(true, 10, 1, 1);
-//        UserSessionModel persistedSession = loadedSessions.get(0);
-//        UserSessionProviderTest.assertSession(persistedSession, session.users().getUserByUsername("user2", realm), "127.0.0.3", started, started, "test-app");
-//
-//        // KEYCLOAK-2431 Assert that userSessionPersister is resistent even to situation, when users are deleted "directly"
-//        UserModel user2 = session.users().getUserByUsername("user2", realm);
-//        session.users().removeUser(realm, user2);
-//
-//        loadedSessions = loadPersistedSessionsPaginated(true, 10, 0, 0);
-//
-//    }
-//
-//    // KEYCLOAK-1999
-//    @Test
-//    public void testNoSessions() {
-//        UserSessionPersisterProvider persister = session.getProvider(UserSessionPersisterProvider.class);
-//        List<UserSessionModel> sessions = persister.loadUserSessions(0, 1, true);
-//        Assert.assertEquals(0, sessions.size());
-//    }
-//
-//
-//    private ClientSessionModel createClientSession(ClientModel client, UserSessionModel userSession, String redirect, String state, Set<String> roles, Set<String> protocolMappers) {
-//        ClientSessionModel clientSession = session.sessions().createClientSession(realm, client);
-//        if (userSession != null) clientSession.setUserSession(userSession);
-//        clientSession.setRedirectUri(redirect);
-//        if (state != null) clientSession.setNote(OIDCLoginProtocol.STATE_PARAM, state);
-//        if (roles != null) clientSession.setRoles(roles);
-//        if (protocolMappers != null) clientSession.setProtocolMappers(protocolMappers);
-//        return clientSession;
-//    }
-//
-//    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);
-//
-//        Set<String> roles = new HashSet<String>();
-//        roles.add("one");
-//        roles.add("two");
-//
-//        Set<String> protocolMappers = new HashSet<String>();
-//        protocolMappers.add("mapper-one");
-//        protocolMappers.add("mapper-two");
-//
-//        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);
-//        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);
-//        createClientSession(realm.getClientByClientId("test-app"), sessions[2], "http://redirect", "state", new HashSet<String>(), new HashSet<String>());
-//
-//        return sessions;
-//    }
-//
-//    private void persistUserSession(UserSessionModel userSession, boolean offline) {
-//        persister.createUserSession(userSession, offline);
-//        for (ClientSessionModel clientSession : userSession.getClientSessions()) {
-//            persister.createClientSession(clientSession, offline);
-//        }
-//    }
-//
-//    private void resetSession() {
-//        kc.stopSession(session, true);
-//        session = kc.startSession();
-//        realm = session.realms().getRealm("test");
-//        persister = session.getProvider(UserSessionPersisterProvider.class);
-//    }
-//
-//    public static void assertSessionLoaded(List<UserSessionModel> sessions, String id, UserModel user, String ipAddress, int started, int lastRefresh, String... clients) {
-//        for (UserSessionModel session : sessions) {
-//            if (session.getId().equals(id)) {
-//                UserSessionProviderTest.assertSession(session, user, ipAddress, started, lastRefresh, clients);
-//                return;
-//            }
-//        }
-//        Assert.fail("Session with ID " + id + " not found in the list");
-//    }
-//
-//    private List<UserSessionModel> loadPersistedSessionsPaginated(boolean offline, int sessionsPerPage, int expectedPageCount, int expectedSessionsCount) {
-//        int count = persister.getUserSessionsCount(offline);
-//
-//        int start = 0;
-//        int pageCount = 0;
-//        boolean next = true;
-//        List<UserSessionModel> result = new ArrayList<>();
-//        while (next && start < count) {
-//            List<UserSessionModel> sess = persister.loadUserSessions(start, sessionsPerPage, offline);
-//            if (sess.size() == 0) {
-//                next = false;
-//            } else {
-//                pageCount++;
-//                start += sess.size();
-//                result.addAll(sess);
-//            }
-//        }
-//
-//        Assert.assertEquals(pageCount, expectedPageCount);
-//        Assert.assertEquals(result.size(), expectedSessionsCount);
-//        return result;
-//    }
+
+
+    @ClassRule
+    public static KeycloakRule kc = new KeycloakRule();
+
+    private KeycloakSession session;
+    private RealmModel realm;
+    private UserSessionPersisterProvider persister;
+
+    @Before
+    public void before() {
+        session = kc.startSession();
+        realm = session.realms().getRealm("test");
+        session.users().addUser(realm, "user1").setEmail("user1@localhost");
+        session.users().addUser(realm, "user2").setEmail("user2@localhost");
+        persister = session.getProvider(UserSessionPersisterProvider.class);
+    }
+
+    @After
+    public void after() {
+        resetSession();
+        session.sessions().removeUserSessions(realm);
+        UserModel user1 = session.users().getUserByUsername("user1", realm);
+        UserModel user2 = session.users().getUserByUsername("user2", realm);
+
+        UserManager um = new UserManager(session);
+        if (user1 != null) {
+            um.removeUser(realm, user1);
+        }
+        if (user2 != null) {
+            um.removeUser(realm, user2);
+        }
+        kc.stopSession(session, true);
+    }
+
+    @Test
+    public void testPersistenceWithLoad() {
+        // Create some sessions in infinispan
+        int started = Time.currentTime();
+        UserSessionModel[] origSessions = createSessions();
+
+        resetSession();
+
+        // Persist 3 created userSessions and clientSessions as offline
+        ClientModel testApp = realm.getClientByClientId("test-app");
+        List<UserSessionModel> userSessions = session.sessions().getUserSessions(realm, testApp);
+        for (UserSessionModel userSession : userSessions) {
+            persistUserSession(userSession, true);
+        }
+
+        // Persist 1 online session
+        UserSessionModel userSession = session.sessions().getUserSession(realm, origSessions[0].getId());
+        persistUserSession(userSession, false);
+
+        resetSession();
+
+        // Assert online session
+        List<UserSessionModel> loadedSessions = loadPersistedSessionsPaginated(false, 1, 1, 1);
+        UserSessionProviderTest.assertSession(loadedSessions.get(0), session.users().getUserByUsername("user1", realm), "127.0.0.1", started, started, "test-app", "third-party");
+
+        // Assert offline sessions
+        loadedSessions = loadPersistedSessionsPaginated(true, 2, 2, 3);
+        UserSessionProviderTest.assertSessions(loadedSessions, origSessions);
+
+        assertSessionLoaded(loadedSessions, origSessions[0].getId(), session.users().getUserByUsername("user1", realm), "127.0.0.1", started, started, "test-app", "third-party");
+        assertSessionLoaded(loadedSessions, origSessions[1].getId(), session.users().getUserByUsername("user1", realm), "127.0.0.2", started, started, "test-app");
+        assertSessionLoaded(loadedSessions, origSessions[2].getId(), session.users().getUserByUsername("user2", realm), "127.0.0.3", started, started, "test-app");
+    }
+
+    @Test
+    public void testUpdateTimestamps() {
+        // Create some sessions in infinispan
+        int started = Time.currentTime();
+        UserSessionModel[] origSessions = createSessions();
+
+        resetSession();
+
+        // Persist 3 created userSessions and clientSessions as offline
+        ClientModel testApp = realm.getClientByClientId("test-app");
+        List<UserSessionModel> userSessions = session.sessions().getUserSessions(realm, testApp);
+        for (UserSessionModel userSession : userSessions) {
+            persistUserSession(userSession, true);
+        }
+
+        // Persist 1 online session
+        UserSessionModel userSession = session.sessions().getUserSession(realm, origSessions[0].getId());
+        persistUserSession(userSession, false);
+
+        resetSession();
+
+        // update timestamps
+        int newTime = started + 50;
+        persister.updateAllTimestamps(newTime);
+
+        // Assert online session
+        List<UserSessionModel> loadedSessions = loadPersistedSessionsPaginated(false, 1, 1, 1);
+        Assert.assertEquals(2, assertTimestampsUpdated(loadedSessions, newTime));
+
+        // Assert offline sessions
+        loadedSessions = loadPersistedSessionsPaginated(true, 2, 2, 3);
+        Assert.assertEquals(4, assertTimestampsUpdated(loadedSessions, newTime));
+    }
+
+    private int assertTimestampsUpdated(List<UserSessionModel> loadedSessions, int expectedTime) {
+        int clientSessionsCount = 0;
+        for (UserSessionModel loadedSession : loadedSessions) {
+            Assert.assertEquals(expectedTime, loadedSession.getLastSessionRefresh());
+            for (AuthenticatedClientSessionModel clientSession : loadedSession.getAuthenticatedClientSessions().values()) {
+                Assert.assertEquals(expectedTime, clientSession.getTimestamp());
+                clientSessionsCount++;
+            }
+        }
+        return clientSessionsCount;
+    }
+
+    @Test
+    public void testUpdateAndRemove() {
+        // Create some sessions in infinispan
+        int started = Time.currentTime();
+        UserSessionModel[] origSessions = createSessions();
+
+        resetSession();
+
+        // Persist 1 offline session
+        UserSessionModel userSession = session.sessions().getUserSession(realm, origSessions[1].getId());
+        persistUserSession(userSession, true);
+
+        resetSession();
+
+        // Load offline session
+        List<UserSessionModel> loadedSessions = loadPersistedSessionsPaginated(true, 10, 1, 1);
+        UserSessionModel persistedSession = loadedSessions.get(0);
+        UserSessionProviderTest.assertSession(persistedSession, session.users().getUserByUsername("user1", realm), "127.0.0.2", started, started, "test-app");
+
+        // Update userSession
+        Time.setOffset(10);
+        try {
+            persistedSession.setLastSessionRefresh(Time.currentTime());
+            persistedSession.setNote("foo", "bar");
+            persistedSession.setState(UserSessionModel.State.LOGGED_IN);
+            persister.updateUserSession(persistedSession, true);
+
+            // create new clientSession
+            AuthenticatedClientSessionModel clientSession = createClientSession(realm.getClientByClientId("third-party"), session.sessions().getUserSession(realm, persistedSession.getId()),
+                    "http://redirect", "state", new HashSet<String>(), new HashSet<String>());
+            persister.createClientSession(clientSession, true);
+
+            resetSession();
+
+            // Assert session updated
+            loadedSessions = loadPersistedSessionsPaginated(true, 10, 1, 1);
+            persistedSession = loadedSessions.get(0);
+            UserSessionProviderTest.assertSession(persistedSession, session.users().getUserByUsername("user1", realm), "127.0.0.2", started, started+10, "test-app", "third-party");
+            Assert.assertEquals("bar", persistedSession.getNote("foo"));
+            Assert.assertEquals(UserSessionModel.State.LOGGED_IN, persistedSession.getState());
+
+            // Remove clientSession
+            persister.removeClientSession(userSession.getId(), realm.getClientByClientId("third-party").getId(), true);
+
+            resetSession();
+
+            // Assert clientSession removed
+            loadedSessions = loadPersistedSessionsPaginated(true, 10, 1, 1);
+            persistedSession = loadedSessions.get(0);
+            UserSessionProviderTest.assertSession(persistedSession, session.users().getUserByUsername("user1", realm), "127.0.0.2", started, started + 10, "test-app");
+
+            // Remove userSession
+            persister.removeUserSession(persistedSession.getId(), true);
+
+            resetSession();
+
+            // Assert nothing found
+            loadPersistedSessionsPaginated(true, 10, 0, 0);
+        } finally {
+            Time.setOffset(0);
+        }
+    }
+
+    @Test
+    public void testOnRealmRemoved() {
+        RealmModel fooRealm = session.realms().createRealm("foo", "foo");
+        fooRealm.addClient("foo-app");
+        session.users().addUser(fooRealm, "user3");
+
+        UserSessionModel userSession = session.sessions().createUserSession(KeycloakModelUtils.generateId(), fooRealm, session.users().getUserByUsername("user3", fooRealm), "user3", "127.0.0.1", "form", true, null, null);
+        createClientSession(fooRealm.getClientByClientId("foo-app"), userSession, "http://redirect", "state", new HashSet<String>(), new HashSet<String>());
+
+        resetSession();
+
+        // Persist offline session
+        fooRealm = session.realms().getRealm("foo");
+        userSession = session.sessions().getUserSession(fooRealm, userSession.getId());
+        persistUserSession(userSession, true);
+
+        resetSession();
+
+        // Assert session was persisted
+        loadPersistedSessionsPaginated(true, 10, 1, 1);
+
+        // Remove realm
+        RealmManager realmMgr = new RealmManager(session);
+        realmMgr.removeRealm(realmMgr.getRealm("foo"));
+
+        resetSession();
+
+        // Assert nothing loaded
+        loadPersistedSessionsPaginated(true, 10, 0, 0);
+    }
+
+    @Test
+    public void testOnClientRemoved() {
+        int started = Time.currentTime();
+
+        RealmModel fooRealm = session.realms().createRealm("foo", "foo");
+        fooRealm.addClient("foo-app");
+        fooRealm.addClient("bar-app");
+        session.users().addUser(fooRealm, "user3");
+
+        UserSessionModel userSession = session.sessions().createUserSession(KeycloakModelUtils.generateId(), fooRealm, session.users().getUserByUsername("user3", fooRealm), "user3", "127.0.0.1", "form", true, null, null);
+        createClientSession(fooRealm.getClientByClientId("foo-app"), userSession, "http://redirect", "state", new HashSet<String>(), new HashSet<String>());
+        createClientSession(fooRealm.getClientByClientId("bar-app"), userSession, "http://redirect", "state", new HashSet<String>(), new HashSet<String>());
+
+        resetSession();
+
+        // Persist offline session
+        fooRealm = session.realms().getRealm("foo");
+        userSession = session.sessions().getUserSession(fooRealm, userSession.getId());
+        persistUserSession(userSession, true);
+
+        resetSession();
+
+        RealmManager realmMgr = new RealmManager(session);
+        ClientManager clientMgr = new ClientManager(realmMgr);
+        fooRealm = realmMgr.getRealm("foo");
+
+        // Assert session was persisted with both clientSessions
+        UserSessionModel persistedSession = loadPersistedSessionsPaginated(true, 10, 1, 1).get(0);
+        UserSessionProviderTest.assertSession(persistedSession, session.users().getUserByUsername("user3", fooRealm), "127.0.0.1", started, started, "foo-app", "bar-app");
+
+        // Remove foo-app client
+        ClientModel client = fooRealm.getClientByClientId("foo-app");
+        clientMgr.removeClient(fooRealm, client);
+
+        resetSession();
+
+        realmMgr = new RealmManager(session);
+        clientMgr = new ClientManager(realmMgr);
+        fooRealm = realmMgr.getRealm("foo");
+
+        // Assert just one bar-app clientSession persisted now
+        persistedSession = loadPersistedSessionsPaginated(true, 10, 1, 1).get(0);
+        UserSessionProviderTest.assertSession(persistedSession, session.users().getUserByUsername("user3", fooRealm), "127.0.0.1", started, started, "bar-app");
+
+        // Remove bar-app client
+        client = fooRealm.getClientByClientId("bar-app");
+        clientMgr.removeClient(fooRealm, client);
+
+        resetSession();
+
+        // Assert nothing loaded - userSession was removed as well because it was last userSession
+        loadPersistedSessionsPaginated(true, 10, 0, 0);
+
+        // Cleanup
+        realmMgr = new RealmManager(session);
+        realmMgr.removeRealm(realmMgr.getRealm("foo"));
+    }
+
+    @Test
+    public void testOnUserRemoved() {
+        // Create some sessions in infinispan
+        int started = Time.currentTime();
+        UserSessionModel[] origSessions = createSessions();
+
+        resetSession();
+
+        // Persist 2 offline sessions of 2 users
+        UserSessionModel userSession1 = session.sessions().getUserSession(realm, origSessions[1].getId());
+        UserSessionModel userSession2 = session.sessions().getUserSession(realm, origSessions[2].getId());
+        persistUserSession(userSession1, true);
+        persistUserSession(userSession2, true);
+
+        resetSession();
+
+        // Load offline sessions
+        List<UserSessionModel> loadedSessions = loadPersistedSessionsPaginated(true, 10, 1, 2);
+
+        // Properly delete user and assert his offlineSession removed
+        UserModel user1 = session.users().getUserByUsername("user1", realm);
+        new UserManager(session).removeUser(realm, user1);
+
+        resetSession();
+
+        Assert.assertEquals(1, persister.getUserSessionsCount(true));
+        loadedSessions = loadPersistedSessionsPaginated(true, 10, 1, 1);
+        UserSessionModel persistedSession = loadedSessions.get(0);
+        UserSessionProviderTest.assertSession(persistedSession, session.users().getUserByUsername("user2", realm), "127.0.0.3", started, started, "test-app");
+
+        // KEYCLOAK-2431 Assert that userSessionPersister is resistent even to situation, when users are deleted "directly"
+        UserModel user2 = session.users().getUserByUsername("user2", realm);
+        session.users().removeUser(realm, user2);
+
+        loadedSessions = loadPersistedSessionsPaginated(true, 10, 0, 0);
+
+    }
+
+    // KEYCLOAK-1999
+    @Test
+    public void testNoSessions() {
+        UserSessionPersisterProvider persister = session.getProvider(UserSessionPersisterProvider.class);
+        List<UserSessionModel> sessions = persister.loadUserSessions(0, 1, true);
+        Assert.assertEquals(0, sessions.size());
+    }
+
+
+    private AuthenticatedClientSessionModel createClientSession(ClientModel client, UserSessionModel userSession, String redirect, String state, Set<String> roles, Set<String> protocolMappers) {
+        AuthenticatedClientSessionModel clientSession = session.sessions().createClientSession(realm, client, userSession);
+        if (userSession != null) clientSession.setUserSession(userSession);
+        clientSession.setRedirectUri(redirect);
+        if (state != null) clientSession.setNote(OIDCLoginProtocol.STATE_PARAM, state);
+        if (roles != null) clientSession.setRoles(roles);
+        if (protocolMappers != null) clientSession.setProtocolMappers(protocolMappers);
+        return clientSession;
+    }
+
+    private UserSessionModel[] createSessions() {
+        UserSessionModel[] sessions = new UserSessionModel[3];
+        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");
+        roles.add("two");
+
+        Set<String> protocolMappers = new HashSet<String>();
+        protocolMappers.add("mapper-one");
+        protocolMappers.add("mapper-two");
+
+        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(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(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>());
+
+        return sessions;
+    }
+
+    private void persistUserSession(UserSessionModel userSession, boolean offline) {
+        persister.createUserSession(userSession, offline);
+        for (AuthenticatedClientSessionModel clientSession : userSession.getAuthenticatedClientSessions().values()) {
+            persister.createClientSession(clientSession, offline);
+        }
+    }
+
+    private void resetSession() {
+        kc.stopSession(session, true);
+        session = kc.startSession();
+        realm = session.realms().getRealm("test");
+        persister = session.getProvider(UserSessionPersisterProvider.class);
+    }
+
+    public static void assertSessionLoaded(List<UserSessionModel> sessions, String id, UserModel user, String ipAddress, int started, int lastRefresh, String... clients) {
+        for (UserSessionModel session : sessions) {
+            if (session.getId().equals(id)) {
+                UserSessionProviderTest.assertSession(session, user, ipAddress, started, lastRefresh, clients);
+                return;
+            }
+        }
+        Assert.fail("Session with ID " + id + " not found in the list");
+    }
+
+    private List<UserSessionModel> loadPersistedSessionsPaginated(boolean offline, int sessionsPerPage, int expectedPageCount, int expectedSessionsCount) {
+        int count = persister.getUserSessionsCount(offline);
+
+        int start = 0;
+        int pageCount = 0;
+        boolean next = true;
+        List<UserSessionModel> result = new ArrayList<>();
+        while (next && start < count) {
+            List<UserSessionModel> sess = persister.loadUserSessions(start, sessionsPerPage, offline);
+            if (sess.size() == 0) {
+                next = false;
+            } else {
+                pageCount++;
+                start += sess.size();
+                result.addAll(sess);
+            }
+        }
+
+        Assert.assertEquals(pageCount, expectedPageCount);
+        Assert.assertEquals(result.size(), expectedSessionsCount);
+        return result;
+    }
 }
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserSessionProviderOfflineTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserSessionProviderOfflineTest.java
index c69f8f9..106f525 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserSessionProviderOfflineTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserSessionProviderOfflineTest.java
@@ -24,13 +24,14 @@ import org.junit.ClassRule;
 import org.junit.Rule;
 import org.junit.Test;
 import org.keycloak.common.util.Time;
+import org.keycloak.models.AuthenticatedClientSessionModel;
 import org.keycloak.models.ClientModel;
-import org.keycloak.models.ClientSessionModel;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.UserModel;
 import org.keycloak.models.UserSessionModel;
 import org.keycloak.models.session.UserSessionPersisterProvider;
+import org.keycloak.models.utils.KeycloakModelUtils;
 import org.keycloak.protocol.oidc.OIDCLoginProtocol;
 import org.keycloak.services.managers.ClientManager;
 import org.keycloak.services.managers.RealmManager;
@@ -41,7 +42,6 @@ import org.keycloak.testsuite.rule.LoggingRule;
 
 import java.util.HashMap;
 import java.util.HashSet;
-import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -51,410 +51,380 @@ import java.util.Set;
  */
 public class UserSessionProviderOfflineTest {
 
-    // TODO:mposolda
-//    @ClassRule
-//    public static KeycloakRule kc = new KeycloakRule();
-//
-//    @Rule
-//    public LoggingRule loggingRule = new LoggingRule(this);
-//
-//    private KeycloakSession session;
-//    private RealmModel realm;
-//    private UserSessionManager sessionManager;
-//    private UserSessionPersisterProvider persister;
-//
-//    @Before
-//    public void before() {
-//        session = kc.startSession();
-//        realm = session.realms().getRealm("test");
-//        session.users().addUser(realm, "user1").setEmail("user1@localhost");
-//        session.users().addUser(realm, "user2").setEmail("user2@localhost");
-//        sessionManager = new UserSessionManager(session);
-//        persister = session.getProvider(UserSessionPersisterProvider.class);
-//    }
-//
-//    @After
-//    public void after() {
-//        resetSession();
-//        session.sessions().removeUserSessions(realm);
-//        UserModel user1 = session.users().getUserByUsername("user1", realm);
-//        UserModel user2 = session.users().getUserByUsername("user2", realm);
-//
-//        UserManager um = new UserManager(session);
-//        um.removeUser(realm, user1);
-//        um.removeUser(realm, user2);
-//        kc.stopSession(session, true);
-//    }
-//
-//
-//    @Test
-//    public void testOfflineSessionsCrud() {
-//        // Create some online sessions in infinispan
-//        int started = Time.currentTime();
-//        UserSessionModel[] origSessions = createSessions();
-//
-//        resetSession();
-//
-//        Map<String, String> offlineSessions = new HashMap<>();
-//
-//        // Persist 3 created userSessions and clientSessions as offline
-//        ClientModel testApp = realm.getClientByClientId("test-app");
-//        List<UserSessionModel> userSessions = session.sessions().getUserSessions(realm, testApp);
-//        for (UserSessionModel userSession : userSessions) {
-//            offlineSessions.putAll(createOfflineSessionIncludeClientSessions(userSession));
-//        }
-//
-//        resetSession();
-//
-//        // Assert all previously saved offline sessions found
-//        for (Map.Entry<String, String> entry : offlineSessions.entrySet()) {
-//            Assert.assertTrue(sessionManager.findOfflineClientSession(realm, entry.getKey()) != null);
-//
-//            UserSessionModel offlineSession = session.sessions().getUserSession(realm, entry.getValue());
-//            boolean found = false;
-//            for (ClientSessionModel clientSession : offlineSession.getClientSessions()) {
-//                if (clientSession.getId().equals(entry.getKey())) {
-//                    found = true;
-//                }
-//            }
-//            Assert.assertTrue(found);
-//        }
-//
-//        // Find clients with offline token
-//        UserModel user1 = session.users().getUserByUsername("user1", realm);
-//        Set<ClientModel> clients = sessionManager.findClientsWithOfflineToken(realm, user1);
-//        Assert.assertEquals(clients.size(), 2);
-//        for (ClientModel client : clients) {
-//            Assert.assertTrue(client.getClientId().equals("test-app") || client.getClientId().equals("third-party"));
-//        }
-//
-//        UserModel user2 = session.users().getUserByUsername("user2", realm);
-//        clients = sessionManager.findClientsWithOfflineToken(realm, user2);
-//        Assert.assertEquals(clients.size(), 1);
-//        Assert.assertTrue(clients.iterator().next().getClientId().equals("test-app"));
-//
-//        // Test count
-//        testApp = realm.getClientByClientId("test-app");
-//        ClientModel thirdparty = realm.getClientByClientId("third-party");
-//        Assert.assertEquals(3, session.sessions().getOfflineSessionsCount(realm, testApp));
-//        Assert.assertEquals(1, session.sessions().getOfflineSessionsCount(realm, thirdparty));
-//
-//        // Revoke "test-app" for user1
-//        sessionManager.revokeOfflineToken(user1, testApp);
-//
-//        resetSession();
-//
-//        // Assert userSession revoked
-//        testApp = realm.getClientByClientId("test-app");
-//        thirdparty = realm.getClientByClientId("third-party");
-//        Assert.assertEquals(1, session.sessions().getOfflineSessionsCount(realm, testApp));
-//        Assert.assertEquals(1, session.sessions().getOfflineSessionsCount(realm, thirdparty));
-//
-//        List<UserSessionModel> testAppSessions = session.sessions().getOfflineUserSessions(realm, testApp, 0, 10);
-//        List<UserSessionModel> thirdpartySessions = session.sessions().getOfflineUserSessions(realm, thirdparty, 0, 10);
-//        Assert.assertEquals(1, testAppSessions.size());
-//        Assert.assertEquals("127.0.0.3", testAppSessions.get(0).getIpAddress());
-//        Assert.assertEquals("user2", testAppSessions.get(0).getUser().getUsername());
-//        Assert.assertEquals(1, thirdpartySessions.size());
-//        Assert.assertEquals("127.0.0.1", thirdpartySessions.get(0).getIpAddress());
-//        Assert.assertEquals("user1", thirdpartySessions.get(0).getUser().getUsername());
-//
-//        user1 = session.users().getUserByUsername("user1", realm);
-//        user2 = session.users().getUserByUsername("user2", realm);
-//        clients = sessionManager.findClientsWithOfflineToken(realm, user1);
-//        Assert.assertEquals(1, clients.size());
-//        Assert.assertEquals("third-party", clients.iterator().next().getClientId());
-//        clients = sessionManager.findClientsWithOfflineToken(realm, user2);
-//        Assert.assertEquals(1, clients.size());
-//        Assert.assertEquals("test-app", clients.iterator().next().getClientId());
-//    }
-//
-//    @Test
-//    public void testOnRealmRemoved() {
-//        RealmModel fooRealm = session.realms().createRealm("foo", "foo");
-//        fooRealm.addClient("foo-app");
-//        session.users().addUser(fooRealm, "user3");
-//
-//        UserSessionModel userSession = session.sessions().createUserSession(fooRealm, session.users().getUserByUsername("user3", fooRealm), "user3", "127.0.0.1", "form", true, null, null);
-//        ClientSessionModel clientSession = createClientSession(fooRealm.getClientByClientId("foo-app"), userSession, "http://redirect", "state", new HashSet<String>(), new HashSet<String>());
-//
-//        resetSession();
-//
-//        // Persist offline session
-//        fooRealm = session.realms().getRealm("foo");
-//        userSession = session.sessions().getUserSession(fooRealm, userSession.getId());
-//        clientSession = session.sessions().getClientSession(fooRealm, clientSession.getId());
-//        sessionManager.createOrUpdateOfflineSession(userSession.getClientSessions().get(0), userSession);
-//
-//        resetSession();
-//
-//        ClientSessionModel offlineClientSession = sessionManager.findOfflineClientSession(fooRealm, clientSession.getId());
-//        Assert.assertEquals("foo-app", offlineClientSession.getClient().getClientId());
-//        Assert.assertEquals("user3", offlineClientSession.getUserSession().getUser().getUsername());
-//        Assert.assertEquals(offlineClientSession.getId(), offlineClientSession.getUserSession().getClientSessions().get(0).getId());
-//
-//        // Remove realm
-//        RealmManager realmMgr = new RealmManager(session);
-//        realmMgr.removeRealm(realmMgr.getRealm("foo"));
-//
-//        resetSession();
-//
-//        fooRealm = session.realms().createRealm("foo", "foo");
-//        fooRealm.addClient("foo-app");
-//        session.users().addUser(fooRealm, "user3");
-//
-//        resetSession();
-//
-//        // Assert nothing loaded
-//        fooRealm = session.realms().getRealm("foo");
-//        Assert.assertNull(sessionManager.findOfflineClientSession(fooRealm, clientSession.getId()));
-//        Assert.assertEquals(0, session.sessions().getOfflineSessionsCount(fooRealm, fooRealm.getClientByClientId("foo-app")));
-//
-//        // Cleanup
-//        realmMgr = new RealmManager(session);
-//        realmMgr.removeRealm(realmMgr.getRealm("foo"));
-//    }
-//
-//    @Test
-//    public void testOnClientRemoved() {
-//        int started = Time.currentTime();
-//
-//        RealmModel fooRealm = session.realms().createRealm("foo", "foo");
-//        fooRealm.addClient("foo-app");
-//        fooRealm.addClient("bar-app");
-//        session.users().addUser(fooRealm, "user3");
-//
-//        UserSessionModel userSession = session.sessions().createUserSession(fooRealm, session.users().getUserByUsername("user3", fooRealm), "user3", "127.0.0.1", "form", true, null, null);
-//        createClientSession(fooRealm.getClientByClientId("foo-app"), userSession, "http://redirect", "state", new HashSet<String>(), new HashSet<String>());
-//        createClientSession(fooRealm.getClientByClientId("bar-app"), userSession, "http://redirect", "state", new HashSet<String>(), new HashSet<String>());
-//
-//        resetSession();
-//
-//        // Create offline session
-//        fooRealm = session.realms().getRealm("foo");
-//        userSession = session.sessions().getUserSession(fooRealm, userSession.getId());
-//        createOfflineSessionIncludeClientSessions(userSession);
-//
-//        resetSession();
-//
-//        RealmManager realmMgr = new RealmManager(session);
-//        ClientManager clientMgr = new ClientManager(realmMgr);
-//        fooRealm = realmMgr.getRealm("foo");
-//
-//        // Assert session was persisted with both clientSessions
-//        UserSessionModel offlineSession = session.sessions().getOfflineUserSession(fooRealm, userSession.getId());
-//        UserSessionProviderTest.assertSession(offlineSession, session.users().getUserByUsername("user3", fooRealm), "127.0.0.1", started, started, "foo-app", "bar-app");
-//
-//        // Remove foo-app client
-//        ClientModel client = fooRealm.getClientByClientId("foo-app");
-//        clientMgr.removeClient(fooRealm, client);
-//
-//        resetSession();
-//
-//        realmMgr = new RealmManager(session);
-//        clientMgr = new ClientManager(realmMgr);
-//        fooRealm = realmMgr.getRealm("foo");
-//
-//        // Assert just one bar-app clientSession persisted now
-//        offlineSession = session.sessions().getOfflineUserSession(fooRealm, userSession.getId());
-//        Assert.assertEquals(1, offlineSession.getClientSessions().size());
-//        Assert.assertEquals("bar-app", offlineSession.getClientSessions().get(0).getClient().getClientId());
-//
-//        // Remove bar-app client
-//        client = fooRealm.getClientByClientId("bar-app");
-//        clientMgr.removeClient(fooRealm, client);
-//
-//        resetSession();
-//
-//        // Assert nothing loaded - userSession was removed as well because it was last userSession
-//        offlineSession = session.sessions().getOfflineUserSession(fooRealm, userSession.getId());
-//        Assert.assertEquals(0, offlineSession.getClientSessions().size());
-//
-//        // Cleanup
-//        realmMgr = new RealmManager(session);
-//        realmMgr.removeRealm(realmMgr.getRealm("foo"));
-//    }
-//
-//    @Test
-//    public void testOnUserRemoved() {
-//        int started = Time.currentTime();
-//
-//        RealmModel fooRealm = session.realms().createRealm("foo", "foo");
-//        fooRealm.addClient("foo-app");
-//        session.users().addUser(fooRealm, "user3");
-//
-//        UserSessionModel userSession = session.sessions().createUserSession(fooRealm, session.users().getUserByUsername("user3", fooRealm), "user3", "127.0.0.1", "form", true, null, null);
-//        ClientSessionModel clientSession = createClientSession(fooRealm.getClientByClientId("foo-app"), userSession, "http://redirect", "state", new HashSet<String>(), new HashSet<String>());
-//
-//        resetSession();
-//
-//        // Create offline session
-//        fooRealm = session.realms().getRealm("foo");
-//        userSession = session.sessions().getUserSession(fooRealm, userSession.getId());
-//        createOfflineSessionIncludeClientSessions(userSession);
-//
-//        resetSession();
-//
-//        RealmManager realmMgr = new RealmManager(session);
-//        fooRealm = realmMgr.getRealm("foo");
-//        UserModel user3 = session.users().getUserByUsername("user3", fooRealm);
-//
-//        // Assert session was persisted with both clientSessions
-//        UserSessionModel offlineSession = session.sessions().getOfflineUserSession(fooRealm, userSession.getId());
-//        UserSessionProviderTest.assertSession(offlineSession, user3, "127.0.0.1", started, started, "foo-app");
-//
-//        // Remove user3
-//        new UserManager(session).removeUser(fooRealm, user3);
-//
-//        resetSession();
-//
-//        // Assert userSession removed as well
-//        Assert.assertNull(session.sessions().getOfflineUserSession(fooRealm, userSession.getId()));
-//        Assert.assertNull(session.sessions().getOfflineClientSession(fooRealm, clientSession.getId()));
-//
-//        // Cleanup
-//        realmMgr = new RealmManager(session);
-//        realmMgr.removeRealm(realmMgr.getRealm("foo"));
-//
-//    }
-//
-//    @Test
-//    public void testExpired() {
-//        // Create some online sessions in infinispan
-//        int started = Time.currentTime();
-//        UserSessionModel[] origSessions = createSessions();
-//
-//        resetSession();
-//
-//        Map<String, String> offlineSessions = new HashMap<>();
-//
-//        // Persist 3 created userSessions and clientSessions as offline
-//        ClientModel testApp = realm.getClientByClientId("test-app");
-//        List<UserSessionModel> userSessions = session.sessions().getUserSessions(realm, testApp);
-//        for (UserSessionModel userSession : userSessions) {
-//            offlineSessions.putAll(createOfflineSessionIncludeClientSessions(userSession));
-//        }
-//
-//        resetSession();
-//
-//        // Assert all previously saved offline sessions found
-//        for (Map.Entry<String, String> entry : offlineSessions.entrySet()) {
-//            Assert.assertTrue(sessionManager.findOfflineClientSession(realm, entry.getKey()) != null);
-//        }
-//
-//        UserSessionModel session0 = session.sessions().getOfflineUserSession(realm, origSessions[0].getId());
-//        Assert.assertNotNull(session0);
-//        List<String> clientSessions = new LinkedList<>();
-//        for (ClientSessionModel clientSession : session0.getClientSessions()) {
-//            clientSessions.add(clientSession.getId());
-//            Assert.assertNotNull(session.sessions().getOfflineClientSession(realm, clientSession.getId()));
-//        }
-//
-//        UserSessionModel session1 = session.sessions().getOfflineUserSession(realm, origSessions[1].getId());
-//        Assert.assertEquals(1, session1.getClientSessions().size());
-//        ClientSessionModel cls1 = session1.getClientSessions().get(0);
-//
-//        // sessions are in persister too
-//        Assert.assertEquals(3, persister.getUserSessionsCount(true));
-//
-//        // Set lastSessionRefresh to session[0] to 0
-//        session0.setLastSessionRefresh(0);
-//
-//        // Set timestamp to cls1 to 0
-//        cls1.setTimestamp(0);
-//
-//        resetSession();
-//
-//        session.sessions().removeExpired(realm);
-//
-//        resetSession();
-//
-//        // assert session0 not found now
-//        Assert.assertNull(session.sessions().getOfflineUserSession(realm, origSessions[0].getId()));
-//        for (String clientSession : clientSessions) {
-//            Assert.assertNull(session.sessions().getOfflineClientSession(realm, origSessions[0].getId()));
-//            offlineSessions.remove(clientSession);
-//        }
-//
-//        // Assert cls1 not found too
-//        for (Map.Entry<String, String> entry : offlineSessions.entrySet()) {
-//            String userSessionId = entry.getValue();
-//            if (userSessionId.equals(session1.getId())) {
-//                Assert.assertFalse(sessionManager.findOfflineClientSession(realm, entry.getKey()) != null);
-//            } else {
-//                Assert.assertTrue(sessionManager.findOfflineClientSession(realm, entry.getKey()) != null);
-//            }
-//        }
-//        Assert.assertEquals(1, persister.getUserSessionsCount(true));
-//
-//        // Expire everything and assert nothing found
-//        Time.setOffset(3000000);
-//        try {
-//            session.sessions().removeExpired(realm);
-//
-//            resetSession();
-//
-//            for (Map.Entry<String, String> entry : offlineSessions.entrySet()) {
-//                Assert.assertTrue(sessionManager.findOfflineClientSession(realm, entry.getKey()) == null);
-//            }
-//            Assert.assertEquals(0, persister.getUserSessionsCount(true));
-//
-//        } finally {
-//            Time.setOffset(0);
-//        }
-//    }
-//
-//    private Map<String, String> createOfflineSessionIncludeClientSessions(UserSessionModel userSession) {
-//        Map<String, String> offlineSessions = new HashMap<>();
-//
-//        for (ClientSessionModel clientSession : userSession.getClientSessions()) {
-//            sessionManager.createOrUpdateOfflineSession(clientSession, userSession);
-//            offlineSessions.put(clientSession.getId(), userSession.getId());
-//        }
-//        return offlineSessions;
-//    }
-//
-//
-//
-//    private void resetSession() {
-//        kc.stopSession(session, true);
-//        session = kc.startSession();
-//        realm = session.realms().getRealm("test");
-//        sessionManager = new UserSessionManager(session);
-//        persister = session.getProvider(UserSessionPersisterProvider.class);
-//    }
-//
-//    private ClientSessionModel createClientSession(ClientModel client, UserSessionModel userSession, String redirect, String state, Set<String> roles, Set<String> protocolMappers) {
-//        ClientSessionModel clientSession = session.sessions().createClientSession(client.getRealm(), client);
-//        if (userSession != null) clientSession.setUserSession(userSession);
-//        clientSession.setRedirectUri(redirect);
-//        if (state != null) clientSession.setNote(OIDCLoginProtocol.STATE_PARAM, state);
-//        if (roles != null) clientSession.setRoles(roles);
-//        if (protocolMappers != null) clientSession.setProtocolMappers(protocolMappers);
-//        return clientSession;
-//    }
-//
-//    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);
-//
-//        Set<String> roles = new HashSet<String>();
-//        roles.add("one");
-//        roles.add("two");
-//
-//        Set<String> protocolMappers = new HashSet<String>();
-//        protocolMappers.add("mapper-one");
-//        protocolMappers.add("mapper-two");
-//
-//        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);
-//        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);
-//        createClientSession(realm.getClientByClientId("test-app"), sessions[2], "http://redirect", "state", new HashSet<String>(), new HashSet<String>());
-//
-//        return sessions;
-//    }
+    @ClassRule
+    public static KeycloakRule kc = new KeycloakRule();
+
+    @Rule
+    public LoggingRule loggingRule = new LoggingRule(this);
+
+    private KeycloakSession session;
+    private RealmModel realm;
+    private UserSessionManager sessionManager;
+    private UserSessionPersisterProvider persister;
+
+    @Before
+    public void before() {
+        session = kc.startSession();
+        realm = session.realms().getRealm("test");
+        session.users().addUser(realm, "user1").setEmail("user1@localhost");
+        session.users().addUser(realm, "user2").setEmail("user2@localhost");
+        sessionManager = new UserSessionManager(session);
+        persister = session.getProvider(UserSessionPersisterProvider.class);
+    }
+
+    @After
+    public void after() {
+        resetSession();
+        session.sessions().removeUserSessions(realm);
+        UserModel user1 = session.users().getUserByUsername("user1", realm);
+        UserModel user2 = session.users().getUserByUsername("user2", realm);
+
+        UserManager um = new UserManager(session);
+        um.removeUser(realm, user1);
+        um.removeUser(realm, user2);
+        kc.stopSession(session, true);
+    }
+
+
+    @Test
+    public void testOfflineSessionsCrud() {
+        // Create some online sessions in infinispan
+        int started = Time.currentTime();
+        UserSessionModel[] origSessions = createSessions();
+
+        resetSession();
+
+        // Key is userSession ID, values are client UUIDS
+        Map<String, Set<String>> offlineSessions = new HashMap<>();
+
+        // Persist 3 created userSessions and clientSessions as offline
+        ClientModel testApp = realm.getClientByClientId("test-app");
+        List<UserSessionModel> userSessions = session.sessions().getUserSessions(realm, testApp);
+        for (UserSessionModel userSession : userSessions) {
+            offlineSessions.put(userSession.getId(), createOfflineSessionIncludeClientSessions(userSession));
+        }
+
+        resetSession();
+
+        // Assert all previously saved offline sessions found
+        for (Map.Entry<String, Set<String>> entry : offlineSessions.entrySet()) {
+            UserSessionModel offlineSession = sessionManager.findOfflineUserSession(realm, entry.getKey());
+            Assert.assertNotNull(offlineSession);
+            Assert.assertEquals(offlineSession.getAuthenticatedClientSessions().keySet(), entry.getValue());
+        }
+
+        // Find clients with offline token
+        UserModel user1 = session.users().getUserByUsername("user1", realm);
+        Set<ClientModel> clients = sessionManager.findClientsWithOfflineToken(realm, user1);
+        Assert.assertEquals(clients.size(), 2);
+        for (ClientModel client : clients) {
+            Assert.assertTrue(client.getClientId().equals("test-app") || client.getClientId().equals("third-party"));
+        }
+
+        UserModel user2 = session.users().getUserByUsername("user2", realm);
+        clients = sessionManager.findClientsWithOfflineToken(realm, user2);
+        Assert.assertEquals(clients.size(), 1);
+        Assert.assertTrue(clients.iterator().next().getClientId().equals("test-app"));
+
+        // Test count
+        testApp = realm.getClientByClientId("test-app");
+        ClientModel thirdparty = realm.getClientByClientId("third-party");
+        Assert.assertEquals(3, session.sessions().getOfflineSessionsCount(realm, testApp));
+        Assert.assertEquals(1, session.sessions().getOfflineSessionsCount(realm, thirdparty));
+
+        // Revoke "test-app" for user1
+        sessionManager.revokeOfflineToken(user1, testApp);
+
+        resetSession();
+
+        // Assert userSession revoked
+        testApp = realm.getClientByClientId("test-app");
+        thirdparty = realm.getClientByClientId("third-party");
+        Assert.assertEquals(1, session.sessions().getOfflineSessionsCount(realm, testApp));
+        Assert.assertEquals(1, session.sessions().getOfflineSessionsCount(realm, thirdparty));
+
+        List<UserSessionModel> testAppSessions = session.sessions().getOfflineUserSessions(realm, testApp, 0, 10);
+        List<UserSessionModel> thirdpartySessions = session.sessions().getOfflineUserSessions(realm, thirdparty, 0, 10);
+        Assert.assertEquals(1, testAppSessions.size());
+        Assert.assertEquals("127.0.0.3", testAppSessions.get(0).getIpAddress());
+        Assert.assertEquals("user2", testAppSessions.get(0).getUser().getUsername());
+        Assert.assertEquals(1, thirdpartySessions.size());
+        Assert.assertEquals("127.0.0.1", thirdpartySessions.get(0).getIpAddress());
+        Assert.assertEquals("user1", thirdpartySessions.get(0).getUser().getUsername());
+
+        user1 = session.users().getUserByUsername("user1", realm);
+        user2 = session.users().getUserByUsername("user2", realm);
+        clients = sessionManager.findClientsWithOfflineToken(realm, user1);
+        Assert.assertEquals(1, clients.size());
+        Assert.assertEquals("third-party", clients.iterator().next().getClientId());
+        clients = sessionManager.findClientsWithOfflineToken(realm, user2);
+        Assert.assertEquals(1, clients.size());
+        Assert.assertEquals("test-app", clients.iterator().next().getClientId());
+    }
+
+    @Test
+    public void testOnRealmRemoved() {
+        RealmModel fooRealm = session.realms().createRealm("foo", "foo");
+        fooRealm.addClient("foo-app");
+        session.users().addUser(fooRealm, "user3");
+
+        UserSessionModel userSession = session.sessions().createUserSession(KeycloakModelUtils.generateId(), fooRealm, session.users().getUserByUsername("user3", fooRealm), "user3", "127.0.0.1", "form", true, null, null);
+        AuthenticatedClientSessionModel clientSession = createClientSession(fooRealm.getClientByClientId("foo-app"), userSession, "http://redirect", "state", new HashSet<String>(), new HashSet<String>());
+
+        resetSession();
+
+        // Persist offline session
+        fooRealm = session.realms().getRealm("foo");
+        userSession = session.sessions().getUserSession(fooRealm, userSession.getId());
+        createOfflineSessionIncludeClientSessions(userSession);
+
+        resetSession();
+
+        UserSessionModel offlineUserSession = sessionManager.findOfflineUserSession(fooRealm, userSession.getId());
+        Assert.assertEquals(offlineUserSession.getAuthenticatedClientSessions().size(), 1);
+        AuthenticatedClientSessionModel offlineClientSession = offlineUserSession.getAuthenticatedClientSessions().values().iterator().next();
+        Assert.assertEquals("foo-app", offlineClientSession.getClient().getClientId());
+        Assert.assertEquals("user3", offlineClientSession.getUserSession().getUser().getUsername());
+
+        // Remove realm
+        RealmManager realmMgr = new RealmManager(session);
+        realmMgr.removeRealm(realmMgr.getRealm("foo"));
+
+        resetSession();
+
+        fooRealm = session.realms().createRealm("foo", "foo");
+        fooRealm.addClient("foo-app");
+        session.users().addUser(fooRealm, "user3");
+
+        resetSession();
+
+        // Assert nothing loaded
+        fooRealm = session.realms().getRealm("foo");
+        Assert.assertNull(sessionManager.findOfflineUserSession(fooRealm, userSession.getId()));
+        Assert.assertEquals(0, session.sessions().getOfflineSessionsCount(fooRealm, fooRealm.getClientByClientId("foo-app")));
+
+        // Cleanup
+        realmMgr = new RealmManager(session);
+        realmMgr.removeRealm(realmMgr.getRealm("foo"));
+    }
+
+    @Test
+    public void testOnClientRemoved() {
+        int started = Time.currentTime();
+
+        RealmModel fooRealm = session.realms().createRealm("foo", "foo");
+        fooRealm.addClient("foo-app");
+        fooRealm.addClient("bar-app");
+        session.users().addUser(fooRealm, "user3");
+
+        UserSessionModel userSession = session.sessions().createUserSession(KeycloakModelUtils.generateId(), fooRealm, session.users().getUserByUsername("user3", fooRealm), "user3", "127.0.0.1", "form", true, null, null);
+        createClientSession(fooRealm.getClientByClientId("foo-app"), userSession, "http://redirect", "state", new HashSet<String>(), new HashSet<String>());
+        createClientSession(fooRealm.getClientByClientId("bar-app"), userSession, "http://redirect", "state", new HashSet<String>(), new HashSet<String>());
+
+        resetSession();
+
+        // Create offline session
+        fooRealm = session.realms().getRealm("foo");
+        userSession = session.sessions().getUserSession(fooRealm, userSession.getId());
+        createOfflineSessionIncludeClientSessions(userSession);
+
+        resetSession();
+
+        RealmManager realmMgr = new RealmManager(session);
+        ClientManager clientMgr = new ClientManager(realmMgr);
+        fooRealm = realmMgr.getRealm("foo");
+
+        // Assert session was persisted with both clientSessions
+        UserSessionModel offlineSession = session.sessions().getOfflineUserSession(fooRealm, userSession.getId());
+        UserSessionProviderTest.assertSession(offlineSession, session.users().getUserByUsername("user3", fooRealm), "127.0.0.1", started, started, "foo-app", "bar-app");
+
+        // Remove foo-app client
+        ClientModel client = fooRealm.getClientByClientId("foo-app");
+        clientMgr.removeClient(fooRealm, client);
+
+        resetSession();
+
+        realmMgr = new RealmManager(session);
+        clientMgr = new ClientManager(realmMgr);
+        fooRealm = realmMgr.getRealm("foo");
+
+        // Assert just one bar-app clientSession persisted now
+        offlineSession = session.sessions().getOfflineUserSession(fooRealm, userSession.getId());
+        Assert.assertEquals(1, offlineSession.getAuthenticatedClientSessions().size());
+        Assert.assertEquals("bar-app", offlineSession.getAuthenticatedClientSessions().values().iterator().next().getClient().getClientId());
+
+        // Remove bar-app client
+        client = fooRealm.getClientByClientId("bar-app");
+        clientMgr.removeClient(fooRealm, client);
+
+        resetSession();
+
+        // Assert nothing loaded - userSession was removed as well because it was last userSession
+        realmMgr = new RealmManager(session);
+        fooRealm = realmMgr.getRealm("foo");
+        offlineSession = session.sessions().getOfflineUserSession(fooRealm, userSession.getId());
+        Assert.assertEquals(0, offlineSession.getAuthenticatedClientSessions().size());
+
+        // Cleanup
+        realmMgr = new RealmManager(session);
+        realmMgr.removeRealm(realmMgr.getRealm("foo"));
+    }
+
+    @Test
+    public void testOnUserRemoved() {
+        int started = Time.currentTime();
+
+        RealmModel fooRealm = session.realms().createRealm("foo", "foo");
+        fooRealm.addClient("foo-app");
+        session.users().addUser(fooRealm, "user3");
+
+        UserSessionModel userSession = session.sessions().createUserSession(KeycloakModelUtils.generateId(), fooRealm, session.users().getUserByUsername("user3", fooRealm), "user3", "127.0.0.1", "form", true, null, null);
+        AuthenticatedClientSessionModel clientSession = createClientSession(fooRealm.getClientByClientId("foo-app"), userSession, "http://redirect", "state", new HashSet<String>(), new HashSet<String>());
+
+        resetSession();
+
+        // Create offline session
+        fooRealm = session.realms().getRealm("foo");
+        userSession = session.sessions().getUserSession(fooRealm, userSession.getId());
+        createOfflineSessionIncludeClientSessions(userSession);
+
+        resetSession();
+
+        RealmManager realmMgr = new RealmManager(session);
+        fooRealm = realmMgr.getRealm("foo");
+        UserModel user3 = session.users().getUserByUsername("user3", fooRealm);
+
+        // Assert session was persisted with both clientSessions
+        UserSessionModel offlineSession = session.sessions().getOfflineUserSession(fooRealm, userSession.getId());
+        UserSessionProviderTest.assertSession(offlineSession, user3, "127.0.0.1", started, started, "foo-app");
+
+        // Remove user3
+        new UserManager(session).removeUser(fooRealm, user3);
+
+        resetSession();
+
+        // Assert userSession removed as well
+        Assert.assertNull(session.sessions().getOfflineUserSession(fooRealm, userSession.getId()));
+
+        // Cleanup
+        realmMgr = new RealmManager(session);
+        realmMgr.removeRealm(realmMgr.getRealm("foo"));
+
+    }
+
+    @Test
+    public void testExpired() {
+        // Create some online sessions in infinispan
+        int started = Time.currentTime();
+        UserSessionModel[] origSessions = createSessions();
+
+        resetSession();
+
+        // Key is userSessionId, value is set of client UUIDS
+        Map<String, Set<String>> offlineSessions = new HashMap<>();
+
+        // Persist 3 created userSessions and clientSessions as offline
+        ClientModel testApp = realm.getClientByClientId("test-app");
+        List<UserSessionModel> userSessions = session.sessions().getUserSessions(realm, testApp);
+        for (UserSessionModel userSession : userSessions) {
+            offlineSessions.put(userSession.getId(), createOfflineSessionIncludeClientSessions(userSession));
+        }
+
+        resetSession();
+
+        // Assert all previously saved offline sessions found
+        for (Map.Entry<String, Set<String>> entry : offlineSessions.entrySet()) {
+            UserSessionModel foundSession = sessionManager.findOfflineUserSession(realm, entry.getKey());
+            Assert.assertEquals(foundSession.getAuthenticatedClientSessions().keySet(), entry.getValue());
+        }
+
+        UserSessionModel session0 = session.sessions().getOfflineUserSession(realm, origSessions[0].getId());
+        Assert.assertNotNull(session0);
+
+        // sessions are in persister too
+        Assert.assertEquals(3, persister.getUserSessionsCount(true));
+
+        // Set lastSessionRefresh to session[0] to 0
+        session0.setLastSessionRefresh(0);
+
+        resetSession();
+
+        session.sessions().removeExpired(realm);
+
+        resetSession();
+
+        // assert session0 not found now
+        Assert.assertNull(session.sessions().getOfflineUserSession(realm, origSessions[0].getId()));
+
+        Assert.assertEquals(2, persister.getUserSessionsCount(true));
+
+        // Expire everything and assert nothing found
+        Time.setOffset(3000000);
+        try {
+            session.sessions().removeExpired(realm);
+
+            resetSession();
+
+            for (String userSessionId : offlineSessions.keySet()) {
+                Assert.assertNull(sessionManager.findOfflineUserSession(realm, userSessionId));
+            }
+            Assert.assertEquals(0, persister.getUserSessionsCount(true));
+
+        } finally {
+            Time.setOffset(0);
+        }
+    }
+
+    private Set<String> createOfflineSessionIncludeClientSessions(UserSessionModel userSession) {
+        Set<String> offlineSessions = new HashSet<>();
+
+        for (AuthenticatedClientSessionModel clientSession : userSession.getAuthenticatedClientSessions().values()) {
+            sessionManager.createOrUpdateOfflineSession(clientSession, userSession);
+            offlineSessions.add(clientSession.getClient().getId());
+        }
+        return offlineSessions;
+    }
+
+
+    private void resetSession() {
+        kc.stopSession(session, true);
+        session = kc.startSession();
+        realm = session.realms().getRealm("test");
+        sessionManager = new UserSessionManager(session);
+        persister = session.getProvider(UserSessionPersisterProvider.class);
+    }
+
+    private AuthenticatedClientSessionModel createClientSession(ClientModel client, UserSessionModel userSession, String redirect, String state, Set<String> roles, Set<String> protocolMappers) {
+        AuthenticatedClientSessionModel clientSession = session.sessions().createClientSession(client.getRealm(), client, userSession);
+        if (userSession != null) clientSession.setUserSession(userSession);
+        clientSession.setRedirectUri(redirect);
+        if (state != null) clientSession.setNote(OIDCLoginProtocol.STATE_PARAM, state);
+        if (roles != null) clientSession.setRoles(roles);
+        if (protocolMappers != null) clientSession.setProtocolMappers(protocolMappers);
+        return clientSession;
+    }
+
+    private UserSessionModel[] createSessions() {
+        UserSessionModel[] sessions = new UserSessionModel[3];
+        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");
+        roles.add("two");
+
+        Set<String> protocolMappers = new HashSet<String>();
+        protocolMappers.add("mapper-one");
+        protocolMappers.add("mapper-two");
+
+        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(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(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>());
+
+        return sessions;
+    }
 }
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 683a490..7d6745e 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
@@ -25,7 +25,6 @@ import org.junit.Test;
 import org.keycloak.common.util.Time;
 import org.keycloak.models.AuthenticatedClientSessionModel;
 import org.keycloak.models.ClientModel;
-import org.keycloak.models.ClientSessionModel;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.UserLoginFailureModel;
@@ -37,8 +36,8 @@ import org.keycloak.models.UserManager;
 import org.keycloak.testsuite.rule.KeycloakRule;
 
 import java.util.Arrays;
+import java.util.HashMap;
 import java.util.HashSet;
-import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -107,21 +106,35 @@ public class UserSessionProviderTest {
     }
 
     @Test
+    public void testRestartSession() {
+        int started = Time.currentTime();
+        UserSessionModel[] sessions = createSessions();
+
+        Time.setOffset(100);
+
+        UserSessionModel userSession = session.sessions().getUserSession(realm, sessions[0].getId());
+        assertSession(userSession, session.users().getUserByUsername("user1", realm), "127.0.0.1", started, started, "test-app", "third-party");
+
+        userSession.restartSession(realm, session.users().getUserByUsername("user2", realm), "user2", "127.0.0.6", "form", true, null, null);
+
+        resetSession();
+
+        userSession = session.sessions().getUserSession(realm, sessions[0].getId());
+        assertSession(userSession, session.users().getUserByUsername("user2", realm), "127.0.0.6", started + 100, started + 100);
+
+        Time.setOffset(0);
+    }
+
+    @Test
     public void testCreateClientSession() {
         UserSessionModel[] sessions = createSessions();
 
-        List<ClientSessionModel> clientSessions = session.sessions().getUserSession(realm, sessions[0].getId()).getClientSessions();
+        Map<String, AuthenticatedClientSessionModel> clientSessions = session.sessions().getUserSession(realm, sessions[0].getId()).getAuthenticatedClientSessions();
         assertEquals(2, clientSessions.size());
 
-        String client1 = realm.getClientByClientId("test-app").getId();
-
-        ClientSessionModel session1;
+        String clientUUID = realm.getClientByClientId("test-app").getId();
 
-        if (clientSessions.get(0).getClient().getId().equals(client1)) {
-            session1 = clientSessions.get(0);
-        } else {
-            session1 = clientSessions.get(1);
-        }
+        AuthenticatedClientSessionModel session1 = clientSessions.get(clientUUID);
 
         assertEquals(null, session1.getAction());
         assertEquals(realm.getClientByClientId("test-app").getClientId(), session1.getClient().getClientId());
@@ -140,21 +153,22 @@ public class UserSessionProviderTest {
     public void testUpdateClientSession() {
         UserSessionModel[] sessions = createSessions();
 
-        String id = sessions[0].getClientSessions().get(0).getId();
+        String userSessionId = sessions[0].getId();
+        String clientUUID = realm.getClientByClientId("test-app").getId();
 
-        ClientSessionModel clientSession = session.sessions().getClientSession(realm, id);
+        AuthenticatedClientSessionModel clientSession = sessions[0].getAuthenticatedClientSessions().get(clientUUID);
 
         int time = clientSession.getTimestamp();
         assertEquals(null, clientSession.getAction());
 
-        clientSession.setAction(ClientSessionModel.Action.CODE_TO_TOKEN.name());
+        clientSession.setAction(AuthenticatedClientSessionModel.Action.CODE_TO_TOKEN.name());
         clientSession.setTimestamp(time + 10);
 
         kc.stopSession(session, true);
         session = kc.startSession();
 
-        ClientSessionModel updated = session.sessions().getClientSession(realm, id);
-        assertEquals(ClientSessionModel.Action.CODE_TO_TOKEN.name(), updated.getAction());
+        AuthenticatedClientSessionModel updated = session.sessions().getUserSession(realm, userSessionId).getAuthenticatedClientSessions().get(clientUUID);
+        assertEquals(AuthenticatedClientSessionModel.Action.CODE_TO_TOKEN.name(), updated.getAction());
         assertEquals(time + 10, updated.getTimestamp());
     }
 
@@ -170,17 +184,12 @@ public class UserSessionProviderTest {
     public void testRemoveUserSessionsByUser() {
         UserSessionModel[] sessions = createSessions();
 
-        List<String> clientSessionsRemoved = new LinkedList<String>();
-        List<String> clientSessionsKept = new LinkedList<String>();
+        Map<String, Integer> clientSessionsKept = new HashMap<>();
         for (UserSessionModel s : sessions) {
             s = session.sessions().getUserSession(realm, s.getId());
 
-            for (ClientSessionModel c : s.getClientSessions()) {
-                if (c.getUserSession().getUser().getUsername().equals("user1")) {
-                    clientSessionsRemoved.add(c.getId());
-                } else {
-                    clientSessionsKept.add(c.getId());
-                }
+            if (!s.getUser().getUsername().equals("user1")) {
+                clientSessionsKept.put(s.getId(),  s.getAuthenticatedClientSessions().keySet().size());
             }
         }
 
@@ -188,13 +197,12 @@ public class UserSessionProviderTest {
         resetSession();
 
         assertTrue(session.sessions().getUserSessions(realm, session.users().getUserByUsername("user1", realm)).isEmpty());
-        assertFalse(session.sessions().getUserSessions(realm, session.users().getUserByUsername("user2", realm)).isEmpty());
+        List<UserSessionModel> userSessions = session.sessions().getUserSessions(realm, session.users().getUserByUsername("user2", realm));
+        assertFalse(userSessions.isEmpty());
 
-        for (String c : clientSessionsRemoved) {
-            assertNull(session.sessions().getClientSession(realm, c));
-        }
-        for (String c : clientSessionsKept) {
-            assertNotNull(session.sessions().getClientSession(realm, c));
+        Assert.assertEquals(userSessions.size(), clientSessionsKept.size());
+        for (UserSessionModel userSession : userSessions) {
+            Assert.assertEquals((int) clientSessionsKept.get(userSession.getId()), userSession.getAuthenticatedClientSessions().size());
         }
     }
 
@@ -202,76 +210,47 @@ public class UserSessionProviderTest {
     public void testRemoveUserSession() {
         UserSessionModel userSession = createSessions()[0];
 
-        List<String> clientSessionsRemoved = new LinkedList<String>();
-        for (ClientSessionModel c : userSession.getClientSessions()) {
-            clientSessionsRemoved.add(c.getId());
-        }
-
         session.sessions().removeUserSession(realm, userSession);
         resetSession();
 
         assertNull(session.sessions().getUserSession(realm, userSession.getId()));
-        for (String c : clientSessionsRemoved) {
-            assertNull(session.sessions().getClientSession(realm, c));
-        }
     }
 
     @Test
     public void testRemoveUserSessionsByRealm() {
         UserSessionModel[] sessions = createSessions();
 
-        List<ClientSessionModel> clientSessions = new LinkedList<ClientSessionModel>();
-        for (UserSessionModel s : sessions) {
-            clientSessions.addAll(s.getClientSessions());
-        }
-
         session.sessions().removeUserSessions(realm);
         resetSession();
 
         assertTrue(session.sessions().getUserSessions(realm, session.users().getUserByUsername("user1", realm)).isEmpty());
         assertTrue(session.sessions().getUserSessions(realm, session.users().getUserByUsername("user2", realm)).isEmpty());
-
-        for (ClientSessionModel c : clientSessions) {
-            assertNull(session.sessions().getClientSession(realm, c.getId()));
-        }
     }
 
     @Test
     public void testOnClientRemoved() {
         UserSessionModel[] sessions = createSessions();
 
-        List<String> clientSessionsRemoved = new LinkedList<String>();
-        List<String> clientSessionsKept = new LinkedList<String>();
+        String thirdPartyClientUUID = realm.getClientByClientId("third-party").getId();
+
+        Map<String, Set<String>> clientSessionsKept = new HashMap<>();
         for (UserSessionModel s : sessions) {
-            s = session.sessions().getUserSession(realm, s.getId());
-            for (ClientSessionModel c : s.getClientSessions()) {
-                if (c.getClient().getClientId().equals("third-party")) {
-                    clientSessionsRemoved.add(c.getId());
-                } else {
-                    clientSessionsKept.add(c.getId());
-                }
-            }
+            Set<String> clientUUIDS = new HashSet<>(s.getAuthenticatedClientSessions().keySet());
+            clientUUIDS.remove(thirdPartyClientUUID); // This client will be later removed, hence his clientSessions too
+            clientSessionsKept.put(s.getId(), clientUUIDS);
         }
 
-        session.sessions().onClientRemoved(realm, realm.getClientByClientId("third-party"));
+        realm.removeClient(thirdPartyClientUUID);
         resetSession();
 
-        for (String c : clientSessionsRemoved) {
-            assertNull(session.sessions().getClientSession(realm, c));
-        }
-        for (String c : clientSessionsKept) {
-            assertNotNull(session.sessions().getClientSession(realm, c));
+        for (UserSessionModel s : sessions) {
+            s = session.sessions().getUserSession(realm, s.getId());
+            Set<String> clientUUIDS = s.getAuthenticatedClientSessions().keySet();
+            assertEquals(clientUUIDS, clientSessionsKept.get(s.getId()));
         }
 
-        session.sessions().onClientRemoved(realm, realm.getClientByClientId("test-app"));
-        resetSession();
-
-        for (String c : clientSessionsRemoved) {
-            assertNull(session.sessions().getClientSession(realm, c));
-        }
-        for (String c : clientSessionsKept) {
-            assertNull(session.sessions().getClientSession(realm, c));
-        }
+        // Revert client
+        realm.addClient("third-party");
     }
 
     @Test
@@ -281,11 +260,12 @@ public class UserSessionProviderTest {
 
         try {
             Set<String> expired = new HashSet<String>();
-            Set<String> expiredClientSessions = new HashSet<String>();
 
             Time.setOffset(-(realm.getSsoSessionMaxLifespan() + 1));
-            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());
+            UserSessionModel userSession = session.sessions().createUserSession(KeycloakModelUtils.generateId(), realm, session.users().getUserByUsername("user1", realm), "user1", "127.0.0.1", "form", true, null, null);
+            expired.add(userSession.getId());
+            AuthenticatedClientSessionModel clientSession = session.sessions().createClientSession(realm, client, userSession);
+            Assert.assertEquals(userSession, clientSession.getUserSession());
 
             Time.setOffset(0);
             UserSessionModel s = session.sessions().createUserSession(KeycloakModelUtils.generateId(), realm, session.users().getUserByUsername("user2", realm), "user2", "127.0.0.1", "form", true, null, null);
@@ -293,15 +273,12 @@ public class UserSessionProviderTest {
             s.setLastSessionRefresh(0);
             expired.add(s.getId());
 
-            ClientSessionModel clSession = session.sessions().createClientSession(realm, client);
-            clSession.setUserSession(s);
-            expiredClientSessions.add(clSession.getId());
-
             Set<String> valid = new HashSet<String>();
             Set<String> validClientSessions = new HashSet<String>();
 
-            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());
+            userSession = session.sessions().createUserSession(KeycloakModelUtils.generateId(), realm, session.users().getUserByUsername("user1", realm), "user1", "127.0.0.1", "form", true, null, null);
+            valid.add(userSession.getId());
+            validClientSessions.add(session.sessions().createClientSession(realm, client, userSession).getId());
 
             resetSession();
 
@@ -311,91 +288,18 @@ public class UserSessionProviderTest {
             for (String e : expired) {
                 assertNull(session.sessions().getUserSession(realm, e));
             }
-            for (String e : expiredClientSessions) {
-                assertNull(session.sessions().getClientSession(realm, e));
-            }
 
             for (String v : valid) {
-                assertNotNull(session.sessions().getUserSession(realm, v));
-            }
-            for (String e : validClientSessions) {
-                assertNotNull(session.sessions().getClientSession(realm, e));
+                UserSessionModel userSessionLoaded = session.sessions().getUserSession(realm, v);
+                assertNotNull(userSessionLoaded);
+                Assert.assertEquals(1, userSessionLoaded.getAuthenticatedClientSessions().size());
+                Assert.assertNotNull(userSessionLoaded.getAuthenticatedClientSessions().get(client.getId()));
             }
         } finally {
             Time.setOffset(0);
         }
     }
 
-    @Test
-    public void testExpireDetachedClientSessions() {
-        try {
-            realm.setAccessCodeLifespan(10);
-            realm.setAccessCodeLifespanUserAction(10);
-            realm.setAccessCodeLifespanLogin(30);
-
-            // Login lifespan is largest
-            String clientSessionId = session.sessions().createClientSession(realm, realm.getClientByClientId("test-app")).getId();
-            resetSession();
-
-            Time.setOffset(25);
-            session.sessions().removeExpired(realm);
-            resetSession();
-
-            assertNotNull(session.sessions().getClientSession(clientSessionId));
-
-            Time.setOffset(35);
-            session.sessions().removeExpired(realm);
-            resetSession();
-
-            assertNull(session.sessions().getClientSession(clientSessionId));
-
-            // User action is largest
-            realm.setAccessCodeLifespanUserAction(40);
-
-            Time.setOffset(0);
-            clientSessionId = session.sessions().createClientSession(realm, realm.getClientByClientId("test-app")).getId();
-            resetSession();
-
-            Time.setOffset(35);
-            session.sessions().removeExpired(realm);
-            resetSession();
-
-            assertNotNull(session.sessions().getClientSession(clientSessionId));
-
-            Time.setOffset(45);
-            session.sessions().removeExpired(realm);
-            resetSession();
-
-            assertNull(session.sessions().getClientSession(clientSessionId));
-
-            // Access code is largest
-            realm.setAccessCodeLifespan(50);
-
-            Time.setOffset(0);
-            clientSessionId = session.sessions().createClientSession(realm, realm.getClientByClientId("test-app")).getId();
-            resetSession();
-
-            Time.setOffset(45);
-            session.sessions().removeExpired(realm);
-            resetSession();
-
-            assertNotNull(session.sessions().getClientSession(clientSessionId));
-
-            Time.setOffset(55);
-            session.sessions().removeExpired(realm);
-            resetSession();
-
-            assertNull(session.sessions().getClientSession(clientSessionId));
-        } finally {
-            Time.setOffset(0);
-
-            realm.setAccessCodeLifespan(60);
-            realm.setAccessCodeLifespanUserAction(300);
-            realm.setAccessCodeLifespanLogin(1800);
-
-        }
-    }
-
     // KEYCLOAK-2508
     @Test
     public void testRemovingExpiredSession() {
@@ -429,12 +333,13 @@ public class UserSessionProviderTest {
             for (int i = 0; i < 25; i++) {
                 Time.setOffset(i);
                 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"));
+                AuthenticatedClientSessionModel clientSession = session.sessions().createClientSession(realm, realm.getClientByClientId("test-app"), userSession);
                 clientSession.setUserSession(userSession);
                 clientSession.setRedirectUri("http://redirect");
                 clientSession.setRoles(new HashSet<String>());
                 clientSession.setNote(OIDCLoginProtocol.STATE_PARAM, "state");
                 clientSession.setTimestamp(userSession.getStarted());
+                userSession.setLastSessionRefresh(userSession.getStarted());
             }
         } finally {
             Time.setOffset(0);
@@ -451,19 +356,21 @@ public class UserSessionProviderTest {
 
     @Test
     public void testCreateAndGetInSameTransaction() {
+        ClientModel client = realm.getClientByClientId("test-app");
         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>());
+        AuthenticatedClientSessionModel clientSession = createClientSession(client, userSession, "http://redirect", "state", new HashSet<String>(), new HashSet<String>());
 
-        Assert.assertNotNull(session.sessions().getUserSession(realm, userSession.getId()));
-        Assert.assertNotNull(session.sessions().getClientSession(realm, clientSession.getId()));
+        UserSessionModel userSessionLoaded = session.sessions().getUserSession(realm, userSession.getId());
+        AuthenticatedClientSessionModel clientSessionLoaded = userSessionLoaded.getAuthenticatedClientSessions().get(client.getId());
+        Assert.assertNotNull(userSessionLoaded);
+        Assert.assertNotNull(clientSessionLoaded);
 
-        Assert.assertEquals(userSession.getId(), clientSession.getUserSession().getId());
-        Assert.assertEquals(1, userSession.getClientSessions().size());
-        Assert.assertEquals(clientSession.getId(), userSession.getClientSessions().get(0).getId());
+        Assert.assertEquals(userSession.getId(), clientSessionLoaded.getUserSession().getId());
+        Assert.assertEquals(1, userSessionLoaded.getAuthenticatedClientSessions().size());
     }
 
     @Test
-    public void testClientLoginSessions() {
+    public void testAuthenticatedClientSessions() {
         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");
@@ -486,8 +393,8 @@ public class UserSessionProviderTest {
         userSession = session.sessions().getUserSession(realm, userSession.getId());
         Map<String, AuthenticatedClientSessionModel> clientSessions = userSession.getAuthenticatedClientSessions();
         Assert.assertEquals(2, clientSessions.size());
-        testClientLoginSession(clientSessions.get(client1.getId()), "test-app", userSession.getId(), "foo1", 100);
-        testClientLoginSession(clientSessions.get(client2.getId()), "third-party", userSession.getId(), "foo2", 200);
+        testAuthenticatedClientSession(clientSessions.get(client1.getId()), "test-app", userSession.getId(), "foo1", 100);
+        testAuthenticatedClientSession(clientSessions.get(client2.getId()), "third-party", userSession.getId(), "foo2", 200);
 
         // Update session1
         clientSessions.get(client1.getId()).setAction("foo1-updated");
@@ -498,7 +405,7 @@ public class UserSessionProviderTest {
         // Ensure updated
         userSession = session.sessions().getUserSession(realm, userSession.getId());
         clientSessions = userSession.getAuthenticatedClientSessions();
-        testClientLoginSession(clientSessions.get(client1.getId()), "test-app", userSession.getId(), "foo1-updated", 100);
+        testAuthenticatedClientSession(clientSessions.get(client1.getId()), "test-app", userSession.getId(), "foo1-updated", 100);
 
         // Rewrite session2
         clientSession2 = session.sessions().createClientSession(realm, client2, userSession);
@@ -512,8 +419,8 @@ public class UserSessionProviderTest {
         userSession = session.sessions().getUserSession(realm, userSession.getId());
         clientSessions = userSession.getAuthenticatedClientSessions();
         Assert.assertEquals(2, clientSessions.size());
-        testClientLoginSession(clientSessions.get(client1.getId()), "test-app", userSession.getId(), "foo1-updated", 100);
-        testClientLoginSession(clientSessions.get(client2.getId()), "third-party", userSession.getId(), "foo2-rewrited", 300);
+        testAuthenticatedClientSession(clientSessions.get(client1.getId()), "test-app", userSession.getId(), "foo1-updated", 100);
+        testAuthenticatedClientSession(clientSessions.get(client2.getId()), "third-party", userSession.getId(), "foo2-rewrited", 300);
 
         // remove session
         clientSession1 = userSession.getAuthenticatedClientSessions().get(client1.getId());
@@ -549,11 +456,11 @@ public class UserSessionProviderTest {
     }
 
 
-    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());
-        Assert.assertEquals(expectedAction, clientLoginSession.getAction());
-        Assert.assertEquals(expectedTimestamp, clientLoginSession.getTimestamp());
+    private void testAuthenticatedClientSession(AuthenticatedClientSessionModel clientSession, String expectedClientId, String expectedUserSessionId, String expectedAction, int expectedTimestamp) {
+        Assert.assertEquals(expectedClientId, clientSession.getClient().getClientId());
+        Assert.assertEquals(expectedUserSessionId, clientSession.getUserSession().getId());
+        Assert.assertEquals(expectedAction, clientSession.getAction());
+        Assert.assertEquals(expectedTimestamp, clientSession.getTimestamp());
     }
 
     private void assertPaginatedSession(RealmModel realm, ClientModel client, int start, int max, int expectedSize) {
@@ -642,9 +549,8 @@ public class UserSessionProviderTest {
         assertNotNull(session.sessions().getUserLoginFailure(realm, "user2"));
     }
 
-    private ClientSessionModel createClientSession(ClientModel client, UserSessionModel userSession, String redirect, String state, Set<String> roles, Set<String> protocolMappers) {
-        ClientSessionModel clientSession = session.sessions().createClientSession(realm, client);
-        if (userSession != null) clientSession.setUserSession(userSession);
+    private AuthenticatedClientSessionModel createClientSession(ClientModel client, UserSessionModel userSession, String redirect, String state, Set<String> roles, Set<String> protocolMappers) {
+        AuthenticatedClientSessionModel clientSession = session.sessions().createClientSession(realm, client, userSession);
         clientSession.setRedirectUri(redirect);
         if (state != null) clientSession.setNote(OIDCLoginProtocol.STATE_PARAM, state);
         if (roles != null) clientSession.setRoles(roles);
@@ -710,9 +616,14 @@ public class UserSessionProviderTest {
         assertTrue(session.getStarted() >= started - 1 && session.getStarted() <= started + 1);
         assertTrue(session.getLastSessionRefresh() >= lastRefresh - 1 && session.getLastSessionRefresh() <= lastRefresh + 1);
 
-        String[] actualClients = new String[session.getClientSessions().size()];
-        for (int i = 0; i < actualClients.length; i++) {
-            actualClients[i] = session.getClientSessions().get(i).getClient().getClientId();
+        String[] actualClients = new String[session.getAuthenticatedClientSessions().size()];
+        int i = 0;
+        for (Map.Entry<String, AuthenticatedClientSessionModel> entry : session.getAuthenticatedClientSessions().entrySet()) {
+            String clientUUID = entry.getKey();
+            AuthenticatedClientSessionModel clientSession = entry.getValue();
+            Assert.assertEquals(clientUUID, clientSession.getClient().getId());
+            actualClients[i] = clientSession.getClient().getClientId();
+            i++;
         }
 
         Arrays.sort(clients);
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/OAuthClient.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/OAuthClient.java
index 46d62b8..2da679e 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/OAuthClient.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/OAuthClient.java
@@ -35,6 +35,7 @@ import org.keycloak.common.util.PemUtils;
 import org.keycloak.constants.AdapterConstants;
 import org.keycloak.jose.jws.JWSInput;
 import org.keycloak.jose.jws.crypto.RSAProvider;
+import org.keycloak.models.utils.KeycloakModelUtils;
 import org.keycloak.protocol.oidc.OIDCLoginProtocolService;
 import org.keycloak.representations.AccessToken;
 import org.keycloak.representations.RefreshToken;
@@ -69,7 +70,9 @@ public class OAuthClient {
 
     private String redirectUri = "http://localhost:8081/app/auth";
 
-    private String state = "mystate";
+    private StateParamProvider state = () -> {
+        return KeycloakModelUtils.generateId();
+    };
 
     private String scope;
 
@@ -438,7 +441,7 @@ public class OAuthClient {
             b.queryParam(OAuth2Constants.REDIRECT_URI, redirectUri);
         }
         if (state != null) {
-            b.queryParam(OAuth2Constants.STATE, state);
+            b.queryParam(OAuth2Constants.STATE, state.getState());
         }
         if(uiLocales != null){
             b.queryParam(OAuth2Constants.UI_LOCALES_PARAM, uiLocales);
@@ -509,8 +512,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;
     }
 
@@ -639,4 +651,10 @@ public class OAuthClient {
         }
     }
 
+    private interface StateParamProvider {
+
+        String getState();
+
+    }
+
 }
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginExpiredPage.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginExpiredPage.java
new file mode 100644
index 0000000..e3ff938
--- /dev/null
+++ b/testsuite/integration/src/test/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/src/test/java/org/keycloak/testsuite/util/cli/AbstractOfflineCacheCommand.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/util/cli/AbstractOfflineCacheCommand.java
index f94cea0..7aea03e 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/util/cli/AbstractOfflineCacheCommand.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/util/cli/AbstractOfflineCacheCommand.java
@@ -47,9 +47,9 @@ public abstract class AbstractOfflineCacheCommand extends AbstractCommand {
     }
 
     protected String toString(UserSessionEntity userSession) {
-        int clientSessionsSize = userSession.getClientSessions()==null ? 0 : userSession.getClientSessions().size();
+        int clientSessionsSize = userSession.getAuthenticatedClientSessions()==null ? 0 : userSession.getAuthenticatedClientSessions().size();
         return "ID: " + userSession.getId() + ", realm: " + userSession.getRealm() + ", lastAccessTime: " + Time.toDate(userSession.getLastSessionRefresh()) +
-                ", clientSessions: " + clientSessionsSize;
+                ", authenticatedClientSessions: " + clientSessionsSize;
     }
 
     protected abstract void doRunCacheCommand(KeycloakSession session, Cache<String, SessionEntity> cache);
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/util/cli/PersistSessionsCommand.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/util/cli/PersistSessionsCommand.java
index 39b4d48..c8b6771 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/util/cli/PersistSessionsCommand.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/util/cli/PersistSessionsCommand.java
@@ -17,8 +17,11 @@
 
 package org.keycloak.testsuite.util.cli;
 
+import java.util.LinkedList;
+import java.util.List;
+
+import org.keycloak.models.AuthenticatedClientSessionModel;
 import org.keycloak.models.ClientModel;
-import org.keycloak.models.ClientSessionModel;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.KeycloakSessionTask;
 import org.keycloak.models.RealmModel;
@@ -27,8 +30,6 @@ import org.keycloak.models.UserSessionModel;
 import org.keycloak.models.session.UserSessionPersisterProvider;
 import org.keycloak.models.utils.KeycloakModelUtils;
 
-import java.util.LinkedList;
-import java.util.List;
 
 /**
  * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
@@ -65,10 +66,9 @@ public class PersistSessionsCommand extends AbstractCommand {
         });
     }
 
-    // TODO:mposolda
+
     private void createSessionsBatch(final int countInThisBatch) {
-        /*final List<String> userSessionIds = new LinkedList<>();
-        final List<String> clientSessionIds = new LinkedList<>();
+        final List<String> userSessionIds = new LinkedList<>();
 
         KeycloakModelUtils.runJobInTransaction(sessionFactory, new KeycloakSessionTask() {
 
@@ -80,13 +80,11 @@ public class PersistSessionsCommand extends AbstractCommand {
                 UserSessionPersisterProvider persister = session.getProvider(UserSessionPersisterProvider.class);
 
                 for (int i = 0; i < countInThisBatch; i++) {
-                    UserSessionModel userSession = session.sessions().createUserSession(realm, john, "john-doh@localhost", "127.0.0.2", "form", true, null, null);
-                    ClientSessionModel clientSession = session.sessions().createClientSession(realm, testApp);
-                    clientSession.setUserSession(userSession);
+                    UserSessionModel userSession = session.sessions().createUserSession(KeycloakModelUtils.generateId(), realm, john, "john-doh@localhost", "127.0.0.2", "form", true, null, null);
+                    AuthenticatedClientSessionModel clientSession = session.sessions().createClientSession(realm, testApp, userSession);
                     clientSession.setRedirectUri("http://redirect");
                     clientSession.setNote("foo", "bar-" + i);
                     userSessionIds.add(userSession.getId());
-                    clientSessionIds.add(clientSession.getId());
                 }
             }
 
@@ -101,6 +99,7 @@ public class PersistSessionsCommand extends AbstractCommand {
             @Override
             public void run(KeycloakSession session) {
                 RealmModel realm = session.realms().getRealmByName("master");
+                ClientModel testApp = realm.getClientByClientId("security-admin-console");
                 UserSessionPersisterProvider persister = session.getProvider(UserSessionPersisterProvider.class);
 
                 int counter = 0;
@@ -108,20 +107,15 @@ public class PersistSessionsCommand extends AbstractCommand {
                     counter++;
                     UserSessionModel userSession = session.sessions().getUserSession(realm, userSessionId);
                     persister.createUserSession(userSession, true);
-                }
-
-                log.infof("%d user sessions persisted. Continue", counter);
 
-                counter = 0;
-                for (String clientSessionId : clientSessionIds) {
-                    counter++;
-                    ClientSessionModel clientSession = session.sessions().getClientSession(realm, clientSessionId);
+                    AuthenticatedClientSessionModel clientSession = userSession.getAuthenticatedClientSessions().get(testApp.getId());
                     persister.createClientSession(clientSession, true);
                 }
-                log.infof("%d client sessions persisted. Continue", counter);
+
+                log.infof("%d user sessions persisted. Continue", counter);
             }
 
-        });*/
+        });
     }
 
     @Override
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 7489ee2..4c89eaa 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
@@ -163,7 +163,9 @@ public class OAuthClient {
         realm = "test";
         clientId = "test-app";
         redirectUri = APP_ROOT + "/auth";
-        state = KeycloakModelUtils::generateId;
+        state = () -> {
+            return KeycloakModelUtils.generateId();
+        };
         scope = null;
         uiLocales = null;
         clientSessionState = null;
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/actions/RequiredActionMultipleActionsTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/actions/RequiredActionMultipleActionsTest.java
index 433b0b1..124620a 100755
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/actions/RequiredActionMultipleActionsTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/actions/RequiredActionMultipleActionsTest.java
@@ -63,46 +63,50 @@ public class RequiredActionMultipleActionsTest extends AbstractTestRealmKeycloak
         loginPage.open();
         loginPage.login("test-user@localhost", "password");
 
-        String sessionId = null;
+        String codeId = null;
         if (changePasswordPage.isCurrent()) {
-            sessionId = updatePassword(sessionId);
+            codeId = updatePassword(codeId);
 
             updateProfilePage.assertCurrent();
-            updateProfile(sessionId);
+            updateProfile(codeId);
         } else if (updateProfilePage.isCurrent()) {
-            sessionId = updateProfile(sessionId);
+            codeId = updateProfile(codeId);
 
             changePasswordPage.assertCurrent();
-            updatePassword(sessionId);
+            updatePassword(codeId);
         } else {
             Assert.fail("Expected to update password and profile before login");
         }
 
         Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
 
-        events.expectLogin().session(sessionId).assertEvent();
+        events.expectLogin().session(codeId).assertEvent();
     }
 
-    public String updatePassword(String sessionId) {
+    public String updatePassword(String codeId) {
         changePasswordPage.changePassword("new-password", "new-password");
 
         AssertEvents.ExpectedEvent expectedEvent = events.expectRequiredAction(EventType.UPDATE_PASSWORD);
-        if (sessionId != null) {
-            expectedEvent.session(sessionId);
+        if (codeId != null) {
+            expectedEvent.detail(Details.CODE_ID, codeId);
         }
-        return expectedEvent.assertEvent().getSessionId();
+        return expectedEvent.assertEvent().getDetails().get(Details.CODE_ID);
     }
 
-    public String updateProfile(String sessionId) {
+    public String updateProfile(String codeId) {
         updateProfilePage.update("New first", "New last", "new@email.com", "test-user@localhost");
 
-        AssertEvents.ExpectedEvent expectedEvent = events.expectRequiredAction(EventType.UPDATE_EMAIL).detail(Details.PREVIOUS_EMAIL, "test-user@localhost").detail(Details.UPDATED_EMAIL, "new@email.com");
-        if (sessionId != null) {
-            expectedEvent.session(sessionId);
+        AssertEvents.ExpectedEvent expectedEvent = events.expectRequiredAction(EventType.UPDATE_EMAIL)
+                .detail(Details.PREVIOUS_EMAIL, "test-user@localhost")
+                .detail(Details.UPDATED_EMAIL, "new@email.com");
+        if (codeId != null) {
+            expectedEvent.detail(Details.CODE_ID, codeId);
         }
-        sessionId = expectedEvent.assertEvent().getSessionId();
-        events.expectRequiredAction(EventType.UPDATE_PROFILE).session(sessionId).assertEvent();
-        return sessionId;
+        codeId = expectedEvent.assertEvent().getDetails().get(Details.CODE_ID);
+        events.expectRequiredAction(EventType.UPDATE_PROFILE)
+                .detail(Details.CODE_ID, codeId)
+                .assertEvent();
+        return codeId;
     }
 
 }
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/actions/RequiredActionTotpSetupTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/actions/RequiredActionTotpSetupTest.java
index f613087..a06079c 100755
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/actions/RequiredActionTotpSetupTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/actions/RequiredActionTotpSetupTest.java
@@ -127,11 +127,12 @@ public class RequiredActionTotpSetupTest extends AbstractTestRealmKeycloakTest {
 
         totpPage.configure(totp.generateTOTP(totpPage.getTotpSecret()));
 
-        String sessionId = events.expectRequiredAction(EventType.UPDATE_TOTP).user(userId).detail(Details.USERNAME, "setuptotp").assertEvent().getSessionId();
+        String authSessionId = events.expectRequiredAction(EventType.UPDATE_TOTP).user(userId).detail(Details.USERNAME, "setuptotp").assertEvent()
+                .getDetails().get(Details.CODE_ID);
 
         Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
 
-        events.expectLogin().user(userId).session(sessionId).detail(Details.USERNAME, "setuptotp").assertEvent();
+        events.expectLogin().user(userId).session(authSessionId).detail(Details.USERNAME, "setuptotp").assertEvent();
     }
 
     @Test
@@ -145,15 +146,16 @@ public class RequiredActionTotpSetupTest extends AbstractTestRealmKeycloakTest {
 
         totpPage.configure(totp.generateTOTP(totpSecret));
 
-        String sessionId = events.expectRequiredAction(EventType.UPDATE_TOTP).assertEvent().getSessionId();
+        String authSessionId = events.expectRequiredAction(EventType.UPDATE_TOTP).assertEvent()
+                .getDetails().get(Details.CODE_ID);
 
         Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
 
-        EventRepresentation loginEvent = events.expectLogin().session(sessionId).assertEvent();
+        EventRepresentation loginEvent = events.expectLogin().session(authSessionId).assertEvent();
 
         oauth.openLogout();
 
-        events.expectLogout(loginEvent.getSessionId()).assertEvent();
+        events.expectLogout(authSessionId).assertEvent();
 
         loginPage.open();
         loginPage.login("test-user@localhost", "password");
@@ -229,7 +231,8 @@ public class RequiredActionTotpSetupTest extends AbstractTestRealmKeycloakTest {
         totpPage.assertCurrent();
         totpPage.configure(totp.generateTOTP(totpPage.getTotpSecret()));
 
-        String sessionId = events.expectRequiredAction(EventType.UPDATE_TOTP).user(userId).detail(Details.USERNAME, "setupTotp2").assertEvent().getSessionId();
+        String sessionId = events.expectRequiredAction(EventType.UPDATE_TOTP).user(userId).detail(Details.USERNAME, "setupTotp2").assertEvent()
+                .getDetails().get(Details.CODE_ID);
 
         Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
 
@@ -260,7 +263,8 @@ public class RequiredActionTotpSetupTest extends AbstractTestRealmKeycloakTest {
         TimeBasedOTP timeBased = new TimeBasedOTP(HmacOTP.HMAC_SHA1, 8, 30, 1);
         totpPage.configure(timeBased.generateTOTP(totpSecret));
 
-        String sessionId = events.expectRequiredAction(EventType.UPDATE_TOTP).assertEvent().getSessionId();
+        String sessionId = events.expectRequiredAction(EventType.UPDATE_TOTP).assertEvent()
+                .getDetails().get(Details.CODE_ID);
 
         Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
 
@@ -311,7 +315,8 @@ public class RequiredActionTotpSetupTest extends AbstractTestRealmKeycloakTest {
         HmacOTP otpgen = new HmacOTP(6, HmacOTP.HMAC_SHA1, 1);
         totpPage.configure(otpgen.generateHOTP(totpSecret, 0));
         String uri = driver.getCurrentUrl();
-        String sessionId = events.expectRequiredAction(EventType.UPDATE_TOTP).assertEvent().getSessionId();
+        String sessionId = events.expectRequiredAction(EventType.UPDATE_TOTP).assertEvent()
+            .getDetails().get(Details.CODE_ID);
 
         Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
 
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/actions/RequiredActionUpdateProfileTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/actions/RequiredActionUpdateProfileTest.java
index 80ee0fe..9c18070 100755
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/actions/RequiredActionUpdateProfileTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/actions/RequiredActionUpdateProfileTest.java
@@ -16,6 +16,7 @@
  */
 package org.keycloak.testsuite.actions;
 
+import org.hamcrest.Matchers;
 import org.jboss.arquillian.graphene.page.Page;
 import org.junit.Assert;
 import org.junit.Before;
@@ -89,12 +90,12 @@ public class RequiredActionUpdateProfileTest extends AbstractTestRealmKeycloakTe
 
         updateProfilePage.update("New first", "New last", "new@email.com", "test-user@localhost");
 
-        String sessionId = events.expectRequiredAction(EventType.UPDATE_EMAIL).detail(Details.PREVIOUS_EMAIL, "test-user@localhost").detail(Details.UPDATED_EMAIL, "new@email.com").assertEvent().getSessionId();
-        events.expectRequiredAction(EventType.UPDATE_PROFILE).session(sessionId).assertEvent();
+        events.expectRequiredAction(EventType.UPDATE_EMAIL).detail(Details.PREVIOUS_EMAIL, "test-user@localhost").detail(Details.UPDATED_EMAIL, "new@email.com").assertEvent();
+       events.expectRequiredAction(EventType.UPDATE_PROFILE).assertEvent();
 
         Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
 
-        events.expectLogin().session(sessionId).assertEvent();
+        events.expectLogin().assertEvent();
 
         // assert user is really updated in persistent store
         UserRepresentation user = ActionUtil.findUserWithAdminClient(adminClient, "test-user@localhost");
@@ -116,19 +117,17 @@ public class RequiredActionUpdateProfileTest extends AbstractTestRealmKeycloakTe
 
         updateProfilePage.update("New first", "New last", "john-doh@localhost", "new");
 
-        String sessionId = events
-                .expectLogin()
+        events.expectLogin()
                 .event(EventType.UPDATE_PROFILE)
                 .detail(Details.USERNAME, "john-doh@localhost")
                 .user(userId)
-                .session(AssertEvents.isUUID())
+                .session(Matchers.nullValue(String.class))
                 .removeDetail(Details.CONSENT)
-                .assertEvent()
-                .getSessionId();
+                .assertEvent();
 
         Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
 
-        events.expectLogin().detail(Details.USERNAME, "john-doh@localhost").user(userId).session(sessionId).assertEvent();
+        events.expectLogin().detail(Details.USERNAME, "john-doh@localhost").user(userId).assertEvent();
 
         // assert user is really updated in persistent store
         UserRepresentation user = ActionUtil.findUserWithAdminClient(adminClient, "new");
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/actions/TermsAndConditionsTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/actions/TermsAndConditionsTest.java
index ab52294..86066e8 100755
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/actions/TermsAndConditionsTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/actions/TermsAndConditionsTest.java
@@ -16,6 +16,7 @@
  */
 package org.keycloak.testsuite.actions;
 
+import org.hamcrest.Matchers;
 import org.jboss.arquillian.graphene.page.Page;
 import org.junit.Assert;
 import org.junit.Before;
@@ -86,11 +87,11 @@ public class TermsAndConditionsTest extends AbstractTestRealmKeycloakTest {
 
         termsPage.acceptTerms();
 
-        String sessionId = events.expectRequiredAction(EventType.CUSTOM_REQUIRED_ACTION).removeDetail(Details.REDIRECT_URI).detail(Details.CUSTOM_REQUIRED_ACTION, TermsAndConditions.PROVIDER_ID).assertEvent().getSessionId();
+        events.expectRequiredAction(EventType.CUSTOM_REQUIRED_ACTION).removeDetail(Details.REDIRECT_URI).detail(Details.CUSTOM_REQUIRED_ACTION, TermsAndConditions.PROVIDER_ID).assertEvent();
 
         Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
 
-        events.expectLogin().session(sessionId).assertEvent();
+        events.expectLogin().assertEvent();
 
         // assert user attribute is properly set
         UserRepresentation user = ActionUtil.findUserWithAdminClient(adminClient, "test-user@localhost");
@@ -123,6 +124,7 @@ public class TermsAndConditionsTest extends AbstractTestRealmKeycloakTest {
         events.expectLogin().event(EventType.CUSTOM_REQUIRED_ACTION_ERROR).detail(Details.CUSTOM_REQUIRED_ACTION, TermsAndConditions.PROVIDER_ID)
                 .error(Errors.REJECTED_BY_USER)
                 .removeDetail(Details.CONSENT)
+                .session(Matchers.nullValue(String.class))
                 .assertEvent();
 
 
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/AssertEvents.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/AssertEvents.java
index 38662d1..098c05a 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/AssertEvents.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/AssertEvents.java
@@ -122,9 +122,9 @@ public class AssertEvents implements TestRule {
                 .session(isUUID());
     }
 
-    // TODO:mposolda codeId is not needed anymore
     public ExpectedEvent expectCodeToToken(String codeId, String sessionId) {
         return expect(EventType.CODE_TO_TOKEN)
+                .detail(Details.CODE_ID, codeId)
                 .detail(Details.TOKEN_ID, isUUID())
                 .detail(Details.REFRESH_TOKEN_ID, isUUID())
                 .detail(Details.REFRESH_TOKEN_TYPE, TokenUtil.TOKEN_TYPE_REFRESH)
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/kerberos/AbstractKerberosTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/kerberos/AbstractKerberosTest.java
index abda821..37b9d72 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/kerberos/AbstractKerberosTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/kerberos/AbstractKerberosTest.java
@@ -40,6 +40,7 @@ import org.apache.http.auth.AuthScope;
 import org.apache.http.auth.Credentials;
 import org.apache.http.client.params.AuthPolicy;
 import org.apache.http.client.utils.URLEncodedUtils;
+import org.apache.http.impl.client.AbstractHttpClient;
 import org.apache.http.impl.client.DefaultHttpClient;
 import org.ietf.jgss.GSSCredential;
 import org.jboss.arquillian.graphene.page.Page;
@@ -347,7 +348,10 @@ public abstract class AbstractKerberosTest extends AbstractAuthTest {
             cleanupApacheHttpClient();
         }
 
-        DefaultHttpClient httpClient = (DefaultHttpClient) new HttpClientBuilder().build();
+        DefaultHttpClient httpClient = (DefaultHttpClient) new HttpClientBuilder()
+                .disableCookieCache(false)
+                .build();
+
         httpClient.getAuthSchemes().register(AuthPolicy.SPNEGO, spnegoSchemeFactory);
 
         if (useSpnego) {
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/kerberos/KerberosStandaloneTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/kerberos/KerberosStandaloneTest.java
index 65e5a3e..2d6d3af 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/kerberos/KerberosStandaloneTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/kerberos/KerberosStandaloneTest.java
@@ -17,18 +17,23 @@
 
 package org.keycloak.testsuite.federation.kerberos;
 
+import java.net.URI;
+import java.net.URL;
 import java.util.List;
 import java.util.Map;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
 
+import javax.ws.rs.client.Entity;
+import javax.ws.rs.core.MultivaluedMap;
 import javax.ws.rs.core.Response;
 
 import org.junit.Assert;
 import org.junit.ClassRule;
 import org.junit.Test;
 import org.keycloak.common.constants.KerberosConstants;
+import org.keycloak.common.util.KeycloakUriBuilder;
 import org.keycloak.common.util.MultivaluedHashMap;
 import org.keycloak.federation.kerberos.CommonKerberosConfig;
 import org.keycloak.federation.kerberos.KerberosConfig;
@@ -148,15 +153,24 @@ public class KerberosStandaloneTest extends AbstractKerberosTest {
         Response spnegoResponse = spnegoLogin("hnelson", "secret");
         String context = spnegoResponse.readEntity(String.class);
         spnegoResponse.close();
+
+        Assert.assertTrue(context.contains("Log in to test"));
+
         Pattern pattern = Pattern.compile("action=\"([^\"]+)\"");
         Matcher m = pattern.matcher(context);
         Assert.assertTrue(m.find());
         String url = m.group(1);
-        driver.navigate().to(url);
-        Assert.assertTrue(loginPage.isCurrent());
-        loginPage.login("test-user@localhost", "password");
-        String pageSource = driver.getPageSource();
-        assertAuthenticationSuccess(driver.getCurrentUrl());
+
+
+        // Follow login with HttpClient. Improve if needed
+        MultivaluedMap<String, String> params = new javax.ws.rs.core.MultivaluedHashMap<>();
+        params.putSingle("username", "test-user@localhost");
+        params.putSingle("password", "password");
+        Response response = client.target(url).request()
+                .post(Entity.form(params));
+
+        URI redirectUri = response.getLocation();
+        assertAuthenticationSuccess(redirectUri.toString());
 
         events.clear();
         testRealmResource().components().add(kerberosProvider);
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
index 6cbe92f..08d8265 100644
--- 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
@@ -254,7 +254,7 @@ public class BrowserButtonsTest extends AbstractTestRealmKeycloakTest {
 
     // KEYCLOAK-4670 - Flow 5
     @Test
-    public void clickBackButtonAfterReturnFromRegister() {
+    public void clickBackButtonAfterReturnFromRegister() throws Exception {
         loginPage.open();
         loginPage.clickRegister();
         registerPage.assertCurrent();
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/MultipleTabsLoginTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/MultipleTabsLoginTest.java
index cec079d..24a70fd 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/MultipleTabsLoginTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/MultipleTabsLoginTest.java
@@ -21,6 +21,8 @@ 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.models.Constants;
 import org.keycloak.models.UserModel;
 import org.keycloak.representations.idm.RealmRepresentation;
 import org.keycloak.representations.idm.UserRepresentation;
@@ -28,6 +30,7 @@ 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.arquillian.AuthServerTestEnricher;
 import org.keycloak.testsuite.pages.AppPage;
 import org.keycloak.testsuite.pages.ErrorPage;
 import org.keycloak.testsuite.pages.InfoPage;
@@ -43,7 +46,7 @@ import org.keycloak.testsuite.util.GreenMailRule;
 import org.keycloak.testsuite.util.UserBuilder;
 
 /**
- * Tries to test multiple browser tabs
+ * Tries to simulate testing with multiple browser tabs
  *
  * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
  */
@@ -245,4 +248,43 @@ public class MultipleTabsLoginTest extends AbstractTestRealmKeycloakTest {
     }
 
 
+    @Test
+    public void loginActionWithoutExecution() throws Exception {
+        oauth.openLoginForm();
+
+        // Manually remove execution from the URL and try to simulate the request just with "code" parameter
+        String actionUrl = driver.getPageSource().split("action=\"")[1].split("\"")[0].replaceAll("&amp;", "&");
+        actionUrl = actionUrl.replaceFirst("&execution=.*", "");
+
+        driver.navigate().to(actionUrl);
+
+        loginExpiredPage.assertCurrent();
+    }
+
+
+    // Same like "loginActionWithoutExecution", but AuthenticationSession is in REQUIRED_ACTIONS action
+    @Test
+    public void loginActionWithoutExecutionInRequiredActions() throws Exception {
+        oauth.openLoginForm();
+        loginPage.assertCurrent();
+
+        loginPage.login("login-test", "password");
+        updatePasswordPage.assertCurrent();
+
+        // Manually remove execution from the URL and try to simulate the request just with "code" parameter
+        String actionUrl = driver.getPageSource().split("action=\"")[1].split("\"")[0].replaceAll("&amp;", "&");
+        actionUrl = actionUrl.replaceFirst("&execution=.*", "");
+
+        driver.navigate().to(actionUrl);
+
+        // Back on updatePasswordPage now
+        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/oauth/AccessTokenTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/AccessTokenTest.java
index 92e68cb..ecf540c 100755
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/AccessTokenTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/AccessTokenTest.java
@@ -210,12 +210,7 @@ public class AccessTokenTest extends AbstractKeycloakTest {
         oauth.redirectUri(AuthServerTestEnricher.getAuthServerContextRoot() + "/auth/admin/test/console/nosuch.html");
         oauth.openLoginForm();
 
-        String actionUrl = driver.getPageSource().split("action=\"")[1].split("\"")[0].replaceAll("&amp;", "&");
-        actionUrl = actionUrl.replaceFirst("&execution=.*", "");
-
-        String loginPageCode = actionUrl.split("code=")[1].split("&")[0];
-
-        driver.navigate().to(actionUrl);
+        String loginPageCode = driver.getPageSource().split("code=")[1].split("&")[0].split("\"")[0];
 
         oauth.fillLoginForm("test-user@localhost", "password");
 
@@ -452,7 +447,7 @@ public class AccessTokenTest extends AbstractKeycloakTest {
         Assert.assertEquals(400, response.getStatusCode());
 
         EventRepresentation event = events.poll();
-        assertNotNull(event.getDetails().get(Details.CODE_ID));
+        assertNull(event.getDetails().get(Details.CODE_ID));
 
         UserManager.realm(adminClient.realm("test")).user(user).removeRequiredAction(UserModel.RequiredAction.UPDATE_PROFILE.toString());
     }
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 0ca7785..7577990 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
@@ -155,7 +155,7 @@ public class AuthorizationCodeTest extends AbstractKeycloakTest {
     }
 
     private void assertCode(String expectedCodeId, String actualCode) {
-        assertEquals(expectedCodeId, actualCode.split("\\.")[1]);
+        assertEquals(expectedCodeId, actualCode.split("\\.")[2]);
     }
 
 }
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/OAuthGrantTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/OAuthGrantTest.java
index e244f9a..c5304ff 100755
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/OAuthGrantTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/OAuthGrantTest.java
@@ -16,8 +16,8 @@
  */
 package org.keycloak.testsuite.oauth;
 
+import org.hamcrest.Matchers;
 import org.jboss.arquillian.graphene.page.Page;
-import org.junit.After;
 import org.junit.Assert;
 import org.junit.Rule;
 import org.junit.Test;
@@ -155,6 +155,7 @@ public class OAuthGrantTest extends AbstractKeycloakTest {
                 .client(THIRD_PARTY_APP)
                 .error("rejected_by_user")
                 .removeDetail(Details.CONSENT)
+                .session(Matchers.nullValue(String.class))
                 .assertEvent();
     }
 
@@ -309,6 +310,7 @@ public class OAuthGrantTest extends AbstractKeycloakTest {
                 .client(THIRD_PARTY_APP)
                 .error("rejected_by_user")
                 .removeDetail(Details.CONSENT)
+                .session(Matchers.nullValue(String.class))
                 .assertEvent();
 
         oauth.scope("foo-role third-party/bar-role");
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 ac8e359..03522d6 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
@@ -304,6 +304,31 @@ public class OIDCAdvancedRequestParamsTest extends AbstractTestRealmKeycloakTest
 
     }
 
+
+    @Test
+    public void promptLoginDifferentUser() throws Exception {
+        String sss = oauth.getLoginFormUrl();
+        System.out.println(sss);
+
+        // Login user
+        loginPage.open();
+        loginPage.login("test-user@localhost", "password");
+        Assert.assertEquals(AppPage.RequestType.AUTH_RESPONSE, appPage.getRequestType());
+
+        EventRepresentation loginEvent = events.expectLogin().detail(Details.USERNAME, "test-user@localhost").assertEvent();
+        IDToken idToken = sendTokenRequestAndGetIDToken(loginEvent);
+
+        // Assert need to re-authenticate with prompt=login
+        driver.navigate().to(oauth.getLoginFormUrl() + "&prompt=login");
+
+        // Authenticate as different user
+        loginPage.assertCurrent();
+        loginPage.login("john-doh@localhost", "password");
+
+        errorPage.assertCurrent();
+        Assert.assertTrue(errorPage.getError().startsWith("You are already authenticated as different user"));
+    }
+
     // DISPLAY & OTHERS
 
     @Test
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/scripts/client-session-test.js b/testsuite/integration-arquillian/tests/base/src/test/resources/scripts/client-session-test.js
index 07a07a1..0fd70d5 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/resources/scripts/client-session-test.js
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/scripts/client-session-test.js
@@ -12,7 +12,7 @@ function authenticate(context) {
         return;
     }
 
-    if (clientSession.getAuthMethod() != "${authMethod}") {
+    if (clientSession.getProtocol() != "${authMethod}") {
         context.failure(AuthenticationFlowError.INVALID_CLIENT_SESSION);
         return;
     }
diff --git a/themes/src/main/resources/theme/base/login/login-verify-email.ftl b/themes/src/main/resources/theme/base/login/login-verify-email.ftl
index 1396351..53caaa3 100755
--- a/themes/src/main/resources/theme/base/login/login-verify-email.ftl
+++ b/themes/src/main/resources/theme/base/login/login-verify-email.ftl
@@ -9,7 +9,7 @@
             ${msg("emailVerifyInstruction1")}
         </p>
         <p class="instruction">
-            ${msg("emailVerifyInstruction2")} <a href="${url.loginEmailVerificationUrl}">${msg("doClickHere")}</a> ${msg("emailVerifyInstruction3")}
+            ${msg("emailVerifyInstruction2")} <a href="${url.loginAction}">${msg("doClickHere")}</a> ${msg("emailVerifyInstruction3")}
         </p>
     </#if>
 </@layout.registrationLayout>
\ No newline at end of file