keycloak-uncached

KEYCLOAK-702 - Added AdapterTokenStore spi. Possibility

10/23/2014 6:58:47 PM

Changes

Details

diff --git a/core/src/main/java/org/keycloak/adapters/AdapterConstants.java b/core/src/main/java/org/keycloak/adapters/AdapterConstants.java
index 6feb655..c9399a5 100755
--- a/core/src/main/java/org/keycloak/adapters/AdapterConstants.java
+++ b/core/src/main/java/org/keycloak/adapters/AdapterConstants.java
@@ -26,4 +26,7 @@ public interface AdapterConstants {
 
     // Attribute passed in registerNode request for register new application cluster node once he joined cluster
     public static final String APPLICATION_CLUSTER_HOST = "application_cluster_host";
+
+    // Cookie used on adapter side to store token info. Used only when tokenStore is 'COOKIE'
+    public static final String KEYCLOAK_ADAPTER_STATE_COOKIE = "KEYCLOAK_ADAPTER_STATE";
 }
diff --git a/core/src/main/java/org/keycloak/enums/TokenStore.java b/core/src/main/java/org/keycloak/enums/TokenStore.java
new file mode 100644
index 0000000..de16ecd
--- /dev/null
+++ b/core/src/main/java/org/keycloak/enums/TokenStore.java
@@ -0,0 +1,9 @@
+package org.keycloak.enums;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public enum TokenStore {
+    SESSION,
+    COOKIE
+}
diff --git a/core/src/main/java/org/keycloak/representations/adapters/config/AdapterConfig.java b/core/src/main/java/org/keycloak/representations/adapters/config/AdapterConfig.java
index 778f9f5..e2fe9fd 100755
--- a/core/src/main/java/org/keycloak/representations/adapters/config/AdapterConfig.java
+++ b/core/src/main/java/org/keycloak/representations/adapters/config/AdapterConfig.java
@@ -18,7 +18,7 @@ import org.codehaus.jackson.annotate.JsonPropertyOrder;
         "allow-any-hostname", "disable-trust-manager", "truststore", "truststore-password",
         "client-keystore", "client-keystore-password", "client-key-password",
         "auth-server-url-for-backend-requests", "always-refresh-token",
-        "register-node-at-startup", "register-node-period"
+        "register-node-at-startup", "register-node-period", "token-store"
 })
 public class AdapterConfig extends BaseAdapterConfig {
 
@@ -46,6 +46,8 @@ public class AdapterConfig extends BaseAdapterConfig {
     protected boolean registerNodeAtStartup = false;
     @JsonProperty("register-node-period")
     protected int registerNodePeriod = -1;
+    @JsonProperty("token-store")
+    protected String tokenStore;
 
     public boolean isAllowAnyHostname() {
         return allowAnyHostname;
@@ -142,4 +144,12 @@ public class AdapterConfig extends BaseAdapterConfig {
     public void setRegisterNodePeriod(int registerNodePeriod) {
         this.registerNodePeriod = registerNodePeriod;
     }
+
+    public String getTokenStore() {
+        return tokenStore;
+    }
+
+    public void setTokenStore(String tokenStore) {
+        this.tokenStore = tokenStore;
+    }
 }
diff --git a/integration/adapter-core/src/main/java/org/keycloak/adapters/AdapterDeploymentContext.java b/integration/adapter-core/src/main/java/org/keycloak/adapters/AdapterDeploymentContext.java
index 95e31d7..9fff2b2 100755
--- a/integration/adapter-core/src/main/java/org/keycloak/adapters/AdapterDeploymentContext.java
+++ b/integration/adapter-core/src/main/java/org/keycloak/adapters/AdapterDeploymentContext.java
@@ -7,6 +7,7 @@ import org.apache.http.client.methods.HttpGet;
 import org.jboss.logging.Logger;
 import org.keycloak.enums.RelativeUrlsUsed;
 import org.keycloak.enums.SslRequired;
+import org.keycloak.enums.TokenStore;
 import org.keycloak.representations.adapters.config.AdapterConfig;
 import org.keycloak.representations.idm.PublishedRealmRepresentation;
 import org.keycloak.util.JsonSerialization;
@@ -258,6 +259,16 @@ public class AdapterDeploymentContext {
         }
 
         @Override
+        public TokenStore getTokenStore() {
+            return delegate.getTokenStore();
+        }
+
+        @Override
+        public void setTokenStore(TokenStore tokenStore) {
+            delegate.setTokenStore(tokenStore);
+        }
+
+        @Override
         public String getStateCookieName() {
             return delegate.getStateCookieName();
         }
diff --git a/integration/adapter-core/src/main/java/org/keycloak/adapters/AdapterTokenStore.java b/integration/adapter-core/src/main/java/org/keycloak/adapters/AdapterTokenStore.java
new file mode 100644
index 0000000..fd2c5c3
--- /dev/null
+++ b/integration/adapter-core/src/main/java/org/keycloak/adapters/AdapterTokenStore.java
@@ -0,0 +1,43 @@
+package org.keycloak.adapters;
+
+import org.keycloak.KeycloakSecurityContext;
+
+/**
+ * Abstraction for storing token info on adapter side. Intended to be per-request object
+ *
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public interface AdapterTokenStore {
+
+    /**
+     * Impl can validate if current token exists and perform refreshing if it exists and is expired
+     */
+    void checkCurrentToken();
+
+    /**
+     * Check if we are logged already (we have already valid and successfully refreshed accessToken). Establish security context if yes
+     *
+     * @param authenticator used for actual request authentication
+     * @return true if we are logged-in already
+     */
+    boolean isCached(RequestAuthenticator authenticator);
+
+    /**
+     * Finish successful OAuth2 login and store validated account
+     *
+     * @param account
+     */
+    void saveAccountInfo(KeycloakAccount account);
+
+    /**
+     * Handle logout on store side and possibly propagate logout call to Keycloak
+     */
+    void logout();
+
+    /**
+     * Callback invoked after successful token refresh
+     *
+     * @param securityContext context where refresh was performed
+     */
+    void refreshCallback(RefreshableKeycloakSecurityContext securityContext);
+}
diff --git a/integration/adapter-core/src/main/java/org/keycloak/adapters/AdapterUtils.java b/integration/adapter-core/src/main/java/org/keycloak/adapters/AdapterUtils.java
index e41a8b5..6e8b97c 100644
--- a/integration/adapter-core/src/main/java/org/keycloak/adapters/AdapterUtils.java
+++ b/integration/adapter-core/src/main/java/org/keycloak/adapters/AdapterUtils.java
@@ -1,6 +1,11 @@
 package org.keycloak.adapters;
 
+import java.util.Collections;
+import java.util.Set;
+
+import org.jboss.logging.Logger;
 import org.keycloak.KeycloakSecurityContext;
+import org.keycloak.representations.AccessToken;
 import org.keycloak.util.UriUtils;
 
 /**
@@ -8,6 +13,8 @@ import org.keycloak.util.UriUtils;
  */
 public class AdapterUtils {
 
+    private static Logger log = Logger.getLogger(AdapterUtils.class);
+
     public static String getOrigin(String browserRequestURL, KeycloakSecurityContext session) {
         if (session instanceof RefreshableKeycloakSecurityContext) {
             KeycloakDeployment deployment = ((RefreshableKeycloakSecurityContext)session).getDeployment();
@@ -26,4 +33,30 @@ public class AdapterUtils {
             return UriUtils.getOrigin(browserRequestURL);
         }
     }
+
+    public static Set<String> getRolesFromSecurityContext(RefreshableKeycloakSecurityContext session) {
+        Set<String> roles = null;
+        AccessToken accessToken = session.getToken();
+        if (session.getDeployment().isUseResourceRoleMappings()) {
+            if (log.isTraceEnabled()) {
+                log.trace("useResourceRoleMappings");
+            }
+            AccessToken.Access access = accessToken.getResourceAccess(session.getDeployment().getResourceName());
+            if (access != null) roles = access.getRoles();
+        } else {
+            if (log.isTraceEnabled()) {
+                log.trace("use realm role mappings");
+            }
+            AccessToken.Access access = accessToken.getRealmAccess();
+            if (access != null) roles = access.getRoles();
+        }
+        if (roles == null) roles = Collections.emptySet();
+        if (log.isTraceEnabled()) {
+            log.trace("Setting roles: ");
+            for (String role : roles) {
+                log.trace("   role: " + role);
+            }
+        }
+        return roles;
+    }
 }
diff --git a/integration/adapter-core/src/main/java/org/keycloak/adapters/CookieTokenStore.java b/integration/adapter-core/src/main/java/org/keycloak/adapters/CookieTokenStore.java
new file mode 100644
index 0000000..23e017d
--- /dev/null
+++ b/integration/adapter-core/src/main/java/org/keycloak/adapters/CookieTokenStore.java
@@ -0,0 +1,89 @@
+package org.keycloak.adapters;
+
+import java.io.IOException;
+
+import org.jboss.logging.Logger;
+import org.keycloak.KeycloakPrincipal;
+import org.keycloak.RSATokenVerifier;
+import org.keycloak.VerificationException;
+import org.keycloak.jose.jws.JWSInput;
+import org.keycloak.representations.AccessToken;
+import org.keycloak.representations.IDToken;
+import org.keycloak.util.KeycloakUriBuilder;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class CookieTokenStore {
+
+    private static final Logger log = Logger.getLogger(CookieTokenStore.class);
+    private static final String DELIM = "@";
+
+    public static void setTokenCookie(KeycloakDeployment deployment, HttpFacade facade, RefreshableKeycloakSecurityContext session) {
+        log.infof("Set new %s cookie now", AdapterConstants.KEYCLOAK_ADAPTER_STATE_COOKIE);
+        String accessToken = session.getTokenString();
+        String idToken = session.getIdTokenString();
+        String refreshToken = session.getRefreshToken();
+        String cookie = new StringBuilder(accessToken).append(DELIM)
+                .append(idToken).append(DELIM)
+                .append(refreshToken).toString();
+
+        String cookiePath = getContextPath(facade);
+        facade.getResponse().setCookie(AdapterConstants.KEYCLOAK_ADAPTER_STATE_COOKIE, cookie, cookiePath, null, -1, deployment.getSslRequired().isRequired(facade.getRequest().getRemoteAddr()), true);
+    }
+
+    public static KeycloakPrincipal<RefreshableKeycloakSecurityContext> getPrincipalFromCookie(KeycloakDeployment deployment, HttpFacade facade, AdapterTokenStore tokenStore) {
+        HttpFacade.Cookie cookie = facade.getRequest().getCookie(AdapterConstants.KEYCLOAK_ADAPTER_STATE_COOKIE);
+        if (cookie == null) {
+            log.debug("Not found adapter state cookie in current request");
+            return null;
+        }
+
+        String cookieVal = cookie.getValue();
+
+        String[] tokens = cookieVal.split(DELIM);
+        if (tokens.length != 3) {
+            log.warnf("Invalid format of %s cookie. Count of tokens: %s, expected 3", AdapterConstants.KEYCLOAK_ADAPTER_STATE_COOKIE, tokens.length);
+            return null;
+        }
+
+        String accessTokenString = tokens[0];
+        String idTokenString = tokens[1];
+        String refreshTokenString = tokens[2];
+
+        try {
+            // Skip check if token is active now. It's supposed to be done later by the caller
+            AccessToken accessToken = RSATokenVerifier.verifyToken(accessTokenString, deployment.getRealmKey(), deployment.getRealm(), false);
+            IDToken idToken;
+            if (idTokenString != null && idTokenString.length() > 0) {
+                JWSInput input = new JWSInput(idTokenString);
+                try {
+                    idToken = input.readJsonContent(IDToken.class);
+                } catch (IOException e) {
+                    throw new VerificationException(e);
+                }
+            } else {
+                idToken = null;
+            }
+
+            log.debug("Token Verification succeeded!");
+            RefreshableKeycloakSecurityContext secContext = new RefreshableKeycloakSecurityContext(deployment, tokenStore, accessTokenString, accessToken, idTokenString, idToken, refreshTokenString);
+            return new KeycloakPrincipal<RefreshableKeycloakSecurityContext>(accessToken.getSubject(), secContext);
+        } catch (VerificationException ve) {
+            log.warn("Failed verify token", ve);
+            return null;
+        }
+    }
+
+    public static void removeCookie(HttpFacade facade) {
+        String cookiePath = getContextPath(facade);
+        facade.getResponse().resetCookie(AdapterConstants.KEYCLOAK_ADAPTER_STATE_COOKIE, cookiePath);
+    }
+
+    private static String getContextPath(HttpFacade facade) {
+        String uri = facade.getRequest().getURI();
+        String path = KeycloakUriBuilder.fromUri(uri).getPath();
+        int index = path.indexOf("/", 1);
+        return index == -1 ? path : path.substring(0, index);
+    }
+}
diff --git a/integration/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeployment.java b/integration/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeployment.java
index c2d60d1..2380684 100755
--- a/integration/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeployment.java
+++ b/integration/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeployment.java
@@ -5,6 +5,7 @@ import org.jboss.logging.Logger;
 import org.keycloak.ServiceUrlConstants;
 import org.keycloak.enums.RelativeUrlsUsed;
 import org.keycloak.enums.SslRequired;
+import org.keycloak.enums.TokenStore;
 import org.keycloak.representations.adapters.config.AdapterConfig;
 import org.keycloak.util.KeycloakUriBuilder;
 
@@ -42,6 +43,7 @@ public class KeycloakDeployment {
 
     protected String scope;
     protected SslRequired sslRequired = SslRequired.ALL;
+    protected TokenStore tokenStore = TokenStore.SESSION;
     protected String stateCookieName = "OAuth_Token_Request_State";
     protected boolean useResourceRoleMappings;
     protected boolean cors;
@@ -236,6 +238,14 @@ public class KeycloakDeployment {
         this.sslRequired = sslRequired;
     }
 
+    public TokenStore getTokenStore() {
+        return tokenStore;
+    }
+
+    public void setTokenStore(TokenStore tokenStore) {
+        this.tokenStore = tokenStore;
+    }
+
     public String getStateCookieName() {
         return stateCookieName;
     }
diff --git a/integration/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeploymentBuilder.java b/integration/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeploymentBuilder.java
index 8913dd4..c79d905 100755
--- a/integration/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeploymentBuilder.java
+++ b/integration/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeploymentBuilder.java
@@ -4,6 +4,7 @@ import org.codehaus.jackson.map.ObjectMapper;
 import org.codehaus.jackson.map.annotate.JsonSerialize;
 import org.jboss.logging.Logger;
 import org.keycloak.enums.SslRequired;
+import org.keycloak.enums.TokenStore;
 import org.keycloak.representations.adapters.config.AdapterConfig;
 import org.keycloak.util.PemUtils;
 import org.keycloak.util.SystemPropertiesJsonParserFactory;
@@ -48,6 +49,11 @@ public class KeycloakDeploymentBuilder {
         } else {
             deployment.setSslRequired(SslRequired.EXTERNAL);
         }
+        if (adapterConfig.getTokenStore() != null) {
+            deployment.setTokenStore(TokenStore.valueOf(adapterConfig.getTokenStore().toUpperCase()));
+        } else {
+            deployment.setTokenStore(TokenStore.SESSION);
+        }
         deployment.setResourceCredentials(adapterConfig.getCredentials());
         deployment.setPublicClient(adapterConfig.isPublicClient());
         deployment.setUseResourceRoleMappings(adapterConfig.isUseResourceRoleMappings());
diff --git a/integration/adapter-core/src/main/java/org/keycloak/adapters/OAuthRequestAuthenticator.java b/integration/adapter-core/src/main/java/org/keycloak/adapters/OAuthRequestAuthenticator.java
index c3c2e6e..312576e 100755
--- a/integration/adapter-core/src/main/java/org/keycloak/adapters/OAuthRequestAuthenticator.java
+++ b/integration/adapter-core/src/main/java/org/keycloak/adapters/OAuthRequestAuthenticator.java
@@ -4,6 +4,7 @@ import org.jboss.logging.Logger;
 import org.keycloak.OAuth2Constants;
 import org.keycloak.RSATokenVerifier;
 import org.keycloak.VerificationException;
+import org.keycloak.enums.TokenStore;
 import org.keycloak.jose.jws.JWSInput;
 import org.keycloak.representations.AccessToken;
 import org.keycloak.representations.AccessTokenResponse;
@@ -255,7 +256,8 @@ public abstract class OAuthRequestAuthenticator {
         AccessTokenResponse tokenResponse = null;
         strippedOauthParametersRequestUri = stripOauthParametersFromRedirect();
         try {
-            String httpSessionId = reqAuthenticator.getHttpSessionId(true);
+            // For COOKIE store we don't have httpSessionId and single sign-out won't be available
+            String httpSessionId = deployment.getTokenStore() == TokenStore.SESSION ? reqAuthenticator.getHttpSessionId(true) : null;
             tokenResponse = ServerRequest.invokeAccessCodeToToken(deployment, code, strippedOauthParametersRequestUri, httpSessionId);
         } catch (ServerRequest.HttpFailure failure) {
             log.error("failed to turn code into token");
diff --git a/integration/adapter-core/src/main/java/org/keycloak/adapters/RefreshableKeycloakSecurityContext.java b/integration/adapter-core/src/main/java/org/keycloak/adapters/RefreshableKeycloakSecurityContext.java
index 3fc5b3d..3cd87cb 100755
--- a/integration/adapter-core/src/main/java/org/keycloak/adapters/RefreshableKeycloakSecurityContext.java
+++ b/integration/adapter-core/src/main/java/org/keycloak/adapters/RefreshableKeycloakSecurityContext.java
@@ -4,6 +4,7 @@ import org.jboss.logging.Logger;
 import org.keycloak.KeycloakSecurityContext;
 import org.keycloak.RSATokenVerifier;
 import org.keycloak.VerificationException;
+import org.keycloak.enums.TokenStore;
 import org.keycloak.representations.AccessToken;
 import org.keycloak.representations.AccessTokenResponse;
 import org.keycloak.representations.IDToken;
@@ -19,14 +20,16 @@ public class RefreshableKeycloakSecurityContext extends KeycloakSecurityContext 
     protected static Logger log = Logger.getLogger(RefreshableKeycloakSecurityContext.class);
 
     protected transient KeycloakDeployment deployment;
+    protected transient AdapterTokenStore tokenStore;
     protected String refreshToken;
 
     public RefreshableKeycloakSecurityContext() {
     }
 
-    public RefreshableKeycloakSecurityContext(KeycloakDeployment deployment, String tokenString, AccessToken token, String idTokenString, IDToken idToken, String refreshToken) {
+    public RefreshableKeycloakSecurityContext(KeycloakDeployment deployment, AdapterTokenStore tokenStore, String tokenString, AccessToken token, String idTokenString, IDToken idToken, String refreshToken) {
         super(tokenString, token, idTokenString, idToken);
         this.deployment = deployment;
+        this.tokenStore = tokenStore;
         this.refreshToken = refreshToken;
     }
 
@@ -42,6 +45,10 @@ public class RefreshableKeycloakSecurityContext extends KeycloakSecurityContext 
         return super.getTokenString();
     }
 
+    public String getRefreshToken() {
+        return refreshToken;
+    }
+
     public void logout(KeycloakDeployment deployment) {
         try {
             ServerRequest.invokeLogout(deployment, refreshToken);
@@ -58,8 +65,9 @@ public class RefreshableKeycloakSecurityContext extends KeycloakSecurityContext 
         return deployment;
     }
 
-    public void setDeployment(KeycloakDeployment deployment) {
+    public void setCurrentRequestInfo(KeycloakDeployment deployment, AdapterTokenStore tokenStore) {
         this.deployment = deployment;
+        this.tokenStore = tokenStore;
     }
 
     /**
@@ -107,8 +115,14 @@ public class RefreshableKeycloakSecurityContext extends KeycloakSecurityContext 
         this.token = token;
         this.refreshToken = response.getRefreshToken();
         this.tokenString = tokenString;
+        tokenStore.refreshCallback(this);
         return true;
     }
 
 
+    protected void updateTokenCookie(KeycloakDeployment deployment, HttpFacade facade) {
+        if (deployment.getTokenStore() == TokenStore.COOKIE) {
+            CookieTokenStore.setTokenCookie(deployment, facade, this);
+        }
+    }
 }
diff --git a/integration/adapter-core/src/main/java/org/keycloak/adapters/RequestAuthenticator.java b/integration/adapter-core/src/main/java/org/keycloak/adapters/RequestAuthenticator.java
index 0b31238..a4da6a2 100755
--- a/integration/adapter-core/src/main/java/org/keycloak/adapters/RequestAuthenticator.java
+++ b/integration/adapter-core/src/main/java/org/keycloak/adapters/RequestAuthenticator.java
@@ -12,12 +12,14 @@ public abstract class RequestAuthenticator {
 
     protected HttpFacade facade;
     protected KeycloakDeployment deployment;
+    protected AdapterTokenStore tokenStore;
     protected AuthChallenge challenge;
     protected int sslRedirectPort;
 
-    public RequestAuthenticator(HttpFacade facade, KeycloakDeployment deployment, int sslRedirectPort) {
+    public RequestAuthenticator(HttpFacade facade, KeycloakDeployment deployment, AdapterTokenStore tokenStore, int sslRedirectPort) {
         this.facade = facade;
         this.deployment = deployment;
+        this.tokenStore = tokenStore;
         this.sslRedirectPort = sslRedirectPort;
     }
 
@@ -58,7 +60,7 @@ public abstract class RequestAuthenticator {
             log.trace("try oauth");
         }
 
-        if (isCached()) {
+        if (tokenStore.isCached(this)) {
             if (verifySSL()) return AuthOutcome.FAILED;
             log.debug("AUTHENTICATED: was cached");
             return AuthOutcome.AUTHENTICATED;
@@ -103,18 +105,17 @@ public abstract class RequestAuthenticator {
     }
 
     protected void completeAuthentication(OAuthRequestAuthenticator oauth) {
-        RefreshableKeycloakSecurityContext session = new RefreshableKeycloakSecurityContext(deployment, oauth.getTokenString(), oauth.getToken(), oauth.getIdTokenString(), oauth.getIdToken(), oauth.getRefreshToken());
+        RefreshableKeycloakSecurityContext session = new RefreshableKeycloakSecurityContext(deployment, tokenStore, oauth.getTokenString(), oauth.getToken(), oauth.getIdTokenString(), oauth.getIdToken(), oauth.getRefreshToken());
         final KeycloakPrincipal<RefreshableKeycloakSecurityContext> principal = new KeycloakPrincipal<RefreshableKeycloakSecurityContext>(oauth.getToken().getSubject(), session);
         completeOAuthAuthentication(principal);
     }
 
     protected abstract void completeOAuthAuthentication(KeycloakPrincipal<RefreshableKeycloakSecurityContext> principal);
     protected abstract void completeBearerAuthentication(KeycloakPrincipal<RefreshableKeycloakSecurityContext> principal);
-    protected abstract boolean isCached();
     protected abstract String getHttpSessionId(boolean create);
 
     protected void completeAuthentication(BearerTokenRequestAuthenticator bearer) {
-        RefreshableKeycloakSecurityContext session = new RefreshableKeycloakSecurityContext(deployment, bearer.getTokenString(), bearer.getToken(), null, null, null);
+        RefreshableKeycloakSecurityContext session = new RefreshableKeycloakSecurityContext(deployment, null, bearer.getTokenString(), bearer.getToken(), null, null, null);
         final KeycloakPrincipal<RefreshableKeycloakSecurityContext> principal = new KeycloakPrincipal<RefreshableKeycloakSecurityContext>(bearer.getToken().getSubject(), session);
         completeBearerAuthentication(principal);
     }
diff --git a/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/CatalinaCookieTokenStore.java b/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/CatalinaCookieTokenStore.java
new file mode 100644
index 0000000..409f9c2
--- /dev/null
+++ b/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/CatalinaCookieTokenStore.java
@@ -0,0 +1,109 @@
+package org.keycloak.adapters.as7;
+
+import java.util.Set;
+
+import org.apache.catalina.connector.Request;
+import org.apache.catalina.realm.GenericPrincipal;
+import org.jboss.logging.Logger;
+import org.keycloak.KeycloakPrincipal;
+import org.keycloak.KeycloakSecurityContext;
+import org.keycloak.adapters.AdapterTokenStore;
+import org.keycloak.adapters.AdapterUtils;
+import org.keycloak.adapters.CookieTokenStore;
+import org.keycloak.adapters.HttpFacade;
+import org.keycloak.adapters.KeycloakAccount;
+import org.keycloak.adapters.KeycloakDeployment;
+import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
+import org.keycloak.adapters.RequestAuthenticator;
+
+/**
+ * per-request object
+ *
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class CatalinaCookieTokenStore implements AdapterTokenStore {
+
+    private static final Logger log = Logger.getLogger(CatalinaCookieTokenStore.class);
+
+    private Request request;
+    private HttpFacade facade;
+    private KeycloakDeployment deployment;
+
+    private KeycloakPrincipal<RefreshableKeycloakSecurityContext> authenticatedPrincipal;
+
+    public CatalinaCookieTokenStore(Request request, HttpFacade facade, KeycloakDeployment deployment) {
+        this.request = request;
+        this.facade = facade;
+        this.deployment = deployment;
+    }
+
+
+    @Override
+    public void checkCurrentToken() {
+       this.authenticatedPrincipal = checkPrincipalFromCookie();
+    }
+
+    @Override
+    public boolean isCached(RequestAuthenticator authenticator) {
+        // Assuming authenticatedPrincipal set by previous call of checkCurrentToken() during this request
+        if (authenticatedPrincipal != null) {
+            log.debug("remote logged in already. Establish state from cookie");
+            RefreshableKeycloakSecurityContext securityContext = authenticatedPrincipal.getKeycloakSecurityContext();
+            securityContext.setCurrentRequestInfo(deployment, this);
+            Set<String> roles = AdapterUtils.getRolesFromSecurityContext(securityContext);
+            GenericPrincipal principal = new CatalinaSecurityContextHelper().createPrincipal(request.getContext().getRealm(), authenticatedPrincipal, roles, securityContext);
+
+            request.setAttribute(KeycloakSecurityContext.class.getName(), securityContext);
+            request.setUserPrincipal(principal);
+            request.setAuthType("KEYCLOAK");
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    @Override
+    public void saveAccountInfo(KeycloakAccount account) {
+        RefreshableKeycloakSecurityContext securityContext = (RefreshableKeycloakSecurityContext)account.getKeycloakSecurityContext();
+        CookieTokenStore.setTokenCookie(deployment, facade, securityContext);
+    }
+
+    @Override
+    public void logout() {
+        CookieTokenStore.removeCookie(facade);
+
+        KeycloakSecurityContext ksc = (KeycloakSecurityContext)request.getAttribute(KeycloakSecurityContext.class.getName());
+        if (ksc instanceof RefreshableKeycloakSecurityContext) {
+            ((RefreshableKeycloakSecurityContext) ksc).logout(deployment);
+        }
+    }
+
+    @Override
+    public void refreshCallback(RefreshableKeycloakSecurityContext secContext) {
+        CookieTokenStore.setTokenCookie(deployment, facade, secContext);
+    }
+
+    /**
+     * Verify if we already have authenticated and active principal in cookie. Perform refresh if it's not active
+     *
+     * @return valid principal
+     */
+    protected KeycloakPrincipal<RefreshableKeycloakSecurityContext> checkPrincipalFromCookie() {
+        KeycloakPrincipal<RefreshableKeycloakSecurityContext> principal = CookieTokenStore.getPrincipalFromCookie(deployment, facade, this);
+        if (principal == null) {
+            log.debug("Account was not in cookie or was invalid");
+            return null;
+        }
+
+        RefreshableKeycloakSecurityContext session = principal.getKeycloakSecurityContext();
+        if (session.isActive() && !session.getDeployment().isAlwaysRefreshToken()) return principal;
+        boolean success = session.refreshExpiredToken(false);
+        if (success && session.isActive()) return principal;
+
+        log.debugf("Cleanup and expire cookie for user %s after failed refresh", principal.getName());
+        request.setUserPrincipal(null);
+        request.setAuthType(null);
+        CookieTokenStore.removeCookie(facade);
+        return null;
+    }
+}
diff --git a/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/CatalinaRequestAuthenticator.java b/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/CatalinaRequestAuthenticator.java
index a1cb95c..74e0e63 100755
--- a/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/CatalinaRequestAuthenticator.java
+++ b/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/CatalinaRequestAuthenticator.java
@@ -1,21 +1,21 @@
 package org.keycloak.adapters.as7;
 
-import org.apache.catalina.Session;
 import org.apache.catalina.authenticator.Constants;
 import org.apache.catalina.connector.Request;
-import org.apache.catalina.realm.GenericPrincipal;
 import org.jboss.logging.Logger;
 import org.keycloak.KeycloakPrincipal;
 import org.keycloak.KeycloakSecurityContext;
+import org.keycloak.adapters.AdapterTokenStore;
+import org.keycloak.adapters.AdapterUtils;
+import org.keycloak.adapters.KeycloakAccount;
 import org.keycloak.adapters.KeycloakDeployment;
 import org.keycloak.adapters.OAuthRequestAuthenticator;
 import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
 import org.keycloak.adapters.RequestAuthenticator;
-import org.keycloak.representations.AccessToken;
+import org.keycloak.enums.TokenStore;
 
 import java.io.IOException;
 import java.security.Principal;
-import java.util.Collections;
 import java.util.Set;
 
 import javax.servlet.http.HttpSession;
@@ -25,18 +25,17 @@ import javax.servlet.http.HttpSession;
  * @version $Revision: 1 $
  */
 public class CatalinaRequestAuthenticator extends RequestAuthenticator {
+
     private static final Logger log = Logger.getLogger(CatalinaRequestAuthenticator.class);
     protected KeycloakAuthenticatorValve valve;
-    protected CatalinaUserSessionManagement userSessionManagement;
     protected Request request;
 
     public CatalinaRequestAuthenticator(KeycloakDeployment deployment,
-                                        KeycloakAuthenticatorValve valve, CatalinaUserSessionManagement userSessionManagement,
+                                        KeycloakAuthenticatorValve valve, AdapterTokenStore tokenStore,
                                         CatalinaHttpFacade facade,
                                         Request request) {
-        super(facade, deployment, request.getConnector().getRedirectPort());
+        super(facade, deployment, tokenStore, request.getConnector().getRedirectPort());
         this.valve = valve;
-        this.userSessionManagement = userSessionManagement;
         this.request = request;
     }
 
@@ -46,7 +45,10 @@ public class CatalinaRequestAuthenticator extends RequestAuthenticator {
             @Override
             protected void saveRequest() {
                 try {
-                    valve.keycloakSaveRequest(request);
+                    // Support saving request just for TokenStore.SESSION TODO: Add to tokenStore spi?
+                    if (deployment.getTokenStore() == TokenStore.SESSION) {
+                        valve.keycloakSaveRequest(request);
+                    }
                 } catch (IOException e) {
                     throw new RuntimeException(e);
                 }
@@ -55,24 +57,36 @@ public class CatalinaRequestAuthenticator extends RequestAuthenticator {
     }
 
     @Override
-    protected void completeOAuthAuthentication(KeycloakPrincipal<RefreshableKeycloakSecurityContext> skp) {
-        RefreshableKeycloakSecurityContext securityContext = skp.getKeycloakSecurityContext();
+    protected void completeOAuthAuthentication(final KeycloakPrincipal<RefreshableKeycloakSecurityContext> skp) {
+        final RefreshableKeycloakSecurityContext securityContext = skp.getKeycloakSecurityContext();
+        final Set<String> roles = AdapterUtils.getRolesFromSecurityContext(securityContext);
+        KeycloakAccount account = new KeycloakAccount() {
+
+            @Override
+            public Principal getPrincipal() {
+                return skp;
+            }
+
+            @Override
+            public Set<String> getRoles() {
+                return roles;
+            }
+
+            @Override
+            public KeycloakSecurityContext getKeycloakSecurityContext() {
+                return securityContext;
+            }
+
+        };
+
         request.setAttribute(KeycloakSecurityContext.class.getName(), securityContext);
-        Set<String> roles = getRolesFromToken(securityContext);
-        GenericPrincipal principal = new CatalinaSecurityContextHelper().createPrincipal(request.getContext().getRealm(), skp, roles, securityContext);
-        Session session = request.getSessionInternal(true);
-        session.setPrincipal(principal);
-        session.setAuthType("OAUTH");
-        session.setNote(KeycloakSecurityContext.class.getName(), securityContext);
-        String username = securityContext.getToken().getSubject();
-        log.debug("userSessionManage.login: " + username);
-        userSessionManagement.login(session);
+        this.tokenStore.saveAccountInfo(account);
     }
 
     @Override
     protected void completeBearerAuthentication(KeycloakPrincipal<RefreshableKeycloakSecurityContext> principal) {
         RefreshableKeycloakSecurityContext securityContext = principal.getKeycloakSecurityContext();
-        Set<String> roles = getRolesFromToken(securityContext);
+        Set<String> roles = AdapterUtils.getRolesFromSecurityContext(securityContext);
         if (log.isDebugEnabled()) {
             log.debug("Completing bearer authentication. Bearer roles: " + roles);
         }
@@ -82,39 +96,6 @@ public class CatalinaRequestAuthenticator extends RequestAuthenticator {
         request.setAttribute(KeycloakSecurityContext.class.getName(), securityContext);
     }
 
-    protected Set<String> getRolesFromToken(RefreshableKeycloakSecurityContext session) {
-        Set<String> roles = null;
-        if (deployment.isUseResourceRoleMappings()) {
-            AccessToken.Access access = session.getToken().getResourceAccess(deployment.getResourceName());
-            if (access != null) roles = access.getRoles();
-        } else {
-            AccessToken.Access access =  session.getToken().getRealmAccess();
-            if (access != null) roles = access.getRoles();
-        }
-        if (roles == null) roles = Collections.emptySet();
-        return roles;
-    }
-
-    @Override
-    protected boolean isCached() {
-        if (request.getSessionInternal(false) == null || request.getSessionInternal().getPrincipal() == null)
-            return false;
-        log.debug("remote logged in already");
-        GenericPrincipal principal = (GenericPrincipal) request.getSessionInternal().getPrincipal();
-        request.setUserPrincipal(principal);
-        request.setAuthType("KEYCLOAK");
-        Session session = request.getSessionInternal();
-        if (session != null) {
-            RefreshableKeycloakSecurityContext securityContext = (RefreshableKeycloakSecurityContext) session.getNote(KeycloakSecurityContext.class.getName());
-            if (securityContext != null) {
-                securityContext.setDeployment(deployment);
-                request.setAttribute(KeycloakSecurityContext.class.getName(), securityContext);
-            }
-        }
-        restoreRequest();
-        return true;
-    }
-
     protected void restoreRequest() {
         if (request.getSessionInternal().getNote(Constants.FORM_REQUEST_NOTE) != null) {
             if (valve.keycloakRestoreRequest(request)) {
diff --git a/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/CatalinaSessionTokenStore.java b/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/CatalinaSessionTokenStore.java
new file mode 100644
index 0000000..08b83ad
--- /dev/null
+++ b/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/CatalinaSessionTokenStore.java
@@ -0,0 +1,110 @@
+package org.keycloak.adapters.as7;
+
+import java.util.Set;
+
+import org.apache.catalina.Session;
+import org.apache.catalina.connector.Request;
+import org.apache.catalina.realm.GenericPrincipal;
+import org.jboss.logging.Logger;
+import org.keycloak.KeycloakSecurityContext;
+import org.keycloak.adapters.AdapterTokenStore;
+import org.keycloak.adapters.KeycloakAccount;
+import org.keycloak.adapters.KeycloakDeployment;
+import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
+import org.keycloak.adapters.RequestAuthenticator;
+
+/**
+ * per-request object
+ *
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class CatalinaSessionTokenStore implements AdapterTokenStore {
+
+    private static final Logger log = Logger.getLogger(CatalinaSessionTokenStore.class);
+
+    private Request request;
+    private KeycloakDeployment deployment;
+    private CatalinaUserSessionManagement sessionManagement;
+
+    public CatalinaSessionTokenStore(Request request, KeycloakDeployment deployment, CatalinaUserSessionManagement sessionManagement) {
+        this.request = request;
+        this.deployment = deployment;
+        this.sessionManagement = sessionManagement;
+    }
+
+    @Override
+    public void checkCurrentToken() {
+        if (request.getSessionInternal(false) == null || request.getSessionInternal().getPrincipal() == null) return;
+        RefreshableKeycloakSecurityContext session = (RefreshableKeycloakSecurityContext) request.getSessionInternal().getNote(KeycloakSecurityContext.class.getName());
+        if (session == null) return;
+        // just in case session got serialized
+        if (session.getDeployment() == null) session.setCurrentRequestInfo(deployment, this);
+        if (session.isActive() && !session.getDeployment().isAlwaysRefreshToken()) return;
+
+        // FYI: A refresh requires same scope, so same roles will be set.  Otherwise, refresh will fail and token will
+        // not be updated
+        boolean success = session.refreshExpiredToken(false);
+        if (success && session.isActive()) return;
+
+        // Refresh failed, so user is already logged out from keycloak. Cleanup and expire our session
+        Session catalinaSession = request.getSessionInternal();
+        log.debugf("Cleanup and expire session %s after failed refresh", catalinaSession.getId());
+        catalinaSession.removeNote(KeycloakSecurityContext.class.getName());
+        request.setUserPrincipal(null);
+        request.setAuthType(null);
+        catalinaSession.setPrincipal(null);
+        catalinaSession.setAuthType(null);
+        catalinaSession.expire();
+    }
+
+    @Override
+    public boolean isCached(RequestAuthenticator authenticator) {
+        if (request.getSessionInternal(false) == null || request.getSessionInternal().getPrincipal() == null)
+            return false;
+        log.debug("remote logged in already. Establish state from session");
+        GenericPrincipal principal = (GenericPrincipal) request.getSessionInternal().getPrincipal();
+        request.setUserPrincipal(principal);
+        request.setAuthType("KEYCLOAK");
+
+        RefreshableKeycloakSecurityContext securityContext = (RefreshableKeycloakSecurityContext) request.getSessionInternal().getNote(KeycloakSecurityContext.class.getName());
+        if (securityContext != null) {
+            securityContext.setCurrentRequestInfo(deployment, this);
+            request.setAttribute(KeycloakSecurityContext.class.getName(), securityContext);
+        }
+
+        ((CatalinaRequestAuthenticator)authenticator).restoreRequest();
+        return true;
+    }
+
+    @Override
+    public void saveAccountInfo(KeycloakAccount account) {
+        RefreshableKeycloakSecurityContext securityContext = (RefreshableKeycloakSecurityContext)account.getKeycloakSecurityContext();
+        Set<String> roles = account.getRoles();
+        GenericPrincipal principal = new CatalinaSecurityContextHelper().createPrincipal(request.getContext().getRealm(), account.getPrincipal(), roles, securityContext);
+
+        Session session = request.getSessionInternal(true);
+        session.setPrincipal(principal);
+        session.setAuthType("OAUTH");
+        session.setNote(KeycloakSecurityContext.class.getName(), securityContext);
+        String username = securityContext.getToken().getSubject();
+        log.debug("userSessionManagement.login: " + username);
+        this.sessionManagement.login(session);
+    }
+
+    @Override
+    public void logout() {
+        Session session = request.getSessionInternal(false);
+        if (session != null) {
+            session.removeNote(KeycloakSecurityContext.class.getName());
+            KeycloakSecurityContext ksc = (KeycloakSecurityContext)request.getAttribute(KeycloakSecurityContext.class.getName());
+            if (ksc instanceof RefreshableKeycloakSecurityContext) {
+                ((RefreshableKeycloakSecurityContext) ksc).logout(deployment);
+            }
+        }
+    }
+
+    @Override
+    public void refreshCallback(RefreshableKeycloakSecurityContext securityContext) {
+        // no-op
+    }
+}
diff --git a/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/KeycloakAuthenticatorValve.java b/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/KeycloakAuthenticatorValve.java
index 8b40abb..93836a4 100755
--- a/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/KeycloakAuthenticatorValve.java
+++ b/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/KeycloakAuthenticatorValve.java
@@ -13,17 +13,21 @@ import org.apache.catalina.connector.Response;
 import org.apache.catalina.core.StandardContext;
 import org.apache.catalina.deploy.LoginConfig;
 import org.jboss.logging.Logger;
+import org.keycloak.KeycloakPrincipal;
 import org.keycloak.KeycloakSecurityContext;
 import org.keycloak.adapters.AdapterConstants;
 import org.keycloak.adapters.AdapterDeploymentContext;
+import org.keycloak.adapters.AdapterTokenStore;
 import org.keycloak.adapters.AuthChallenge;
 import org.keycloak.adapters.AuthOutcome;
+import org.keycloak.adapters.CookieTokenStore;
 import org.keycloak.adapters.HttpFacade;
 import org.keycloak.adapters.KeycloakDeployment;
 import org.keycloak.adapters.KeycloakDeploymentBuilder;
 import org.keycloak.adapters.NodesRegistrationManagement;
 import org.keycloak.adapters.PreAuthActionsHandler;
 import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
+import org.keycloak.enums.TokenStore;
 
 import javax.servlet.ServletContext;
 import javax.servlet.ServletException;
@@ -44,6 +48,9 @@ import java.io.InputStream;
  * @version $Revision: 1 $
  */
 public class KeycloakAuthenticatorValve extends FormAuthenticator implements LifecycleListener {
+
+    public static final String TOKEN_STORE_NOTE = "TOKEN_STORE_NOTE";
+
     private static final Logger log = Logger.getLogger(KeycloakAuthenticatorValve.class);
     protected CatalinaUserSessionManagement userSessionManagement = new CatalinaUserSessionManagement();
     protected AdapterDeploymentContext deploymentContext;
@@ -63,14 +70,11 @@ public class KeycloakAuthenticatorValve extends FormAuthenticator implements Lif
         KeycloakSecurityContext ksc = (KeycloakSecurityContext)request.getAttribute(KeycloakSecurityContext.class.getName());
         if (ksc != null) {
             request.removeAttribute(KeycloakSecurityContext.class.getName());
-            Session session = request.getSessionInternal(false);
-            if (session != null) {
-                session.removeNote(KeycloakSecurityContext.class.getName());
-                if (ksc instanceof RefreshableKeycloakSecurityContext) {
-                    CatalinaHttpFacade facade = new CatalinaHttpFacade(request, null);
-                    ((RefreshableKeycloakSecurityContext)ksc).logout(deploymentContext.resolveDeployment(facade));
-                }
-            }
+            CatalinaHttpFacade facade = new CatalinaHttpFacade(request, null);
+            KeycloakDeployment deployment = deploymentContext.resolveDeployment(facade);
+
+            AdapterTokenStore tokenStore = getTokenStore(request, facade, deployment);
+            tokenStore.logout();
         }
         super.logout(request);
     }
@@ -164,10 +168,11 @@ public class KeycloakAuthenticatorValve extends FormAuthenticator implements Lif
             log.info("*** deployment isn't configured return false");
             return false;
         }
+        AdapterTokenStore tokenStore = getTokenStore(request, facade, deployment);
 
         nodesRegistrationManagement.tryRegister(deployment);
 
-        CatalinaRequestAuthenticator authenticator = new CatalinaRequestAuthenticator(deployment, this, userSessionManagement, facade, request);
+        CatalinaRequestAuthenticator authenticator = new CatalinaRequestAuthenticator(deployment, this, tokenStore, facade, request);
         AuthOutcome outcome = authenticator.authenticate();
         if (outcome == AuthOutcome.AUTHENTICATED) {
             if (facade.isEnded()) {
@@ -188,27 +193,9 @@ public class KeycloakAuthenticatorValve extends FormAuthenticator implements Lif
      * @param request
      */
     protected void checkKeycloakSession(Request request, HttpFacade facade) {
-        if (request.getSessionInternal(false) == null || request.getSessionInternal().getPrincipal() == null) return;
-        RefreshableKeycloakSecurityContext session = (RefreshableKeycloakSecurityContext) request.getSessionInternal().getNote(KeycloakSecurityContext.class.getName());
-        if (session == null) return;
-        // just in case session got serialized
-        if (session.getDeployment() == null) session.setDeployment(deploymentContext.resolveDeployment(facade));
-        if (session.isActive() && !session.getDeployment().isAlwaysRefreshToken()) return;
-
-        // FYI: A refresh requires same scope, so same roles will be set.  Otherwise, refresh will fail and token will
-        // not be updated
-        boolean success = session.refreshExpiredToken(false);
-        if (success && session.isActive()) return;
-
-        // Refresh failed, so user is already logged out from keycloak. Cleanup and expire our session
-        Session catalinaSession = request.getSessionInternal();
-        log.debugf("Cleanup and expire session %s after failed refresh", catalinaSession.getId());
-        catalinaSession.removeNote(KeycloakSecurityContext.class.getName());
-        request.setUserPrincipal(null);
-        request.setAuthType(null);
-        catalinaSession.setPrincipal(null);
-        catalinaSession.setAuthType(null);
-        catalinaSession.expire();
+        KeycloakDeployment deployment = deploymentContext.resolveDeployment(facade);
+        AdapterTokenStore tokenStore = getTokenStore(request, facade, deployment);
+        tokenStore.checkCurrentToken();
     }
 
     public void keycloakSaveRequest(Request request) throws IOException {
@@ -223,4 +210,20 @@ public class KeycloakAuthenticatorValve extends FormAuthenticator implements Lif
         }
     }
 
+    protected AdapterTokenStore getTokenStore(Request request, HttpFacade facade, KeycloakDeployment resolvedDeployment) {
+        AdapterTokenStore store = (AdapterTokenStore)request.getNote(TOKEN_STORE_NOTE);
+        if (store != null) {
+            return store;
+        }
+
+        if (resolvedDeployment.getTokenStore() == TokenStore.SESSION) {
+            store = new CatalinaSessionTokenStore(request, resolvedDeployment, userSessionManagement);
+        } else {
+            store = new CatalinaCookieTokenStore(request, facade, resolvedDeployment);
+        }
+
+        request.setNote(TOKEN_STORE_NOTE, store);
+        return store;
+    }
+
 }
diff --git a/integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/CatalinaCookieTokenStore.java b/integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/CatalinaCookieTokenStore.java
new file mode 100644
index 0000000..dec12b9
--- /dev/null
+++ b/integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/CatalinaCookieTokenStore.java
@@ -0,0 +1,107 @@
+package org.keycloak.adapters.tomcat7;
+
+import java.util.Set;
+import java.util.logging.Logger;
+
+import org.apache.catalina.connector.Request;
+import org.apache.catalina.realm.GenericPrincipal;
+import org.keycloak.KeycloakPrincipal;
+import org.keycloak.KeycloakSecurityContext;
+import org.keycloak.adapters.AdapterTokenStore;
+import org.keycloak.adapters.AdapterUtils;
+import org.keycloak.adapters.CookieTokenStore;
+import org.keycloak.adapters.HttpFacade;
+import org.keycloak.adapters.KeycloakAccount;
+import org.keycloak.adapters.KeycloakDeployment;
+import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
+import org.keycloak.adapters.RequestAuthenticator;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class CatalinaCookieTokenStore implements AdapterTokenStore {
+
+    private static final Logger log = Logger.getLogger(""+CatalinaCookieTokenStore.class);
+
+    private Request request;
+    private HttpFacade facade;
+    private KeycloakDeployment deployment;
+
+    private KeycloakPrincipal<RefreshableKeycloakSecurityContext> authenticatedPrincipal;
+
+    public CatalinaCookieTokenStore(Request request, HttpFacade facade, KeycloakDeployment deployment) {
+        this.request = request;
+        this.facade = facade;
+        this.deployment = deployment;
+    }
+
+
+    @Override
+    public void checkCurrentToken() {
+        this.authenticatedPrincipal = checkPrincipalFromCookie();
+    }
+
+    @Override
+    public boolean isCached(RequestAuthenticator authenticator) {
+        // Assuming authenticatedPrincipal set by previous call of checkCurrentToken() during this request
+        if (authenticatedPrincipal != null) {
+            log.fine("remote logged in already. Establish state from cookie");
+            RefreshableKeycloakSecurityContext securityContext = authenticatedPrincipal.getKeycloakSecurityContext();
+            securityContext.setCurrentRequestInfo(deployment, this);
+            Set<String> roles = AdapterUtils.getRolesFromSecurityContext(securityContext);
+            GenericPrincipal principal = new CatalinaSecurityContextHelper().createPrincipal(request.getContext().getRealm(), authenticatedPrincipal, roles, securityContext);
+
+            request.setAttribute(KeycloakSecurityContext.class.getName(), securityContext);
+            request.setUserPrincipal(principal);
+            request.setAuthType("KEYCLOAK");
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    @Override
+    public void saveAccountInfo(KeycloakAccount account) {
+        RefreshableKeycloakSecurityContext securityContext = (RefreshableKeycloakSecurityContext)account.getKeycloakSecurityContext();
+        CookieTokenStore.setTokenCookie(deployment, facade, securityContext);
+    }
+
+    @Override
+    public void logout() {
+        CookieTokenStore.removeCookie(facade);
+
+        KeycloakSecurityContext ksc = (KeycloakSecurityContext)request.getAttribute(KeycloakSecurityContext.class.getName());
+        if (ksc instanceof RefreshableKeycloakSecurityContext) {
+            ((RefreshableKeycloakSecurityContext) ksc).logout(deployment);
+        }
+    }
+
+    @Override
+    public void refreshCallback(RefreshableKeycloakSecurityContext secContext) {
+        CookieTokenStore.setTokenCookie(deployment, facade, secContext);
+    }
+
+    /**
+     * Verify if we already have authenticated and active principal in cookie. Perform refresh if it's not active
+     *
+     * @return valid principal
+     */
+    protected KeycloakPrincipal<RefreshableKeycloakSecurityContext> checkPrincipalFromCookie() {
+        KeycloakPrincipal<RefreshableKeycloakSecurityContext> principal = CookieTokenStore.getPrincipalFromCookie(deployment, facade, this);
+        if (principal == null) {
+            log.fine("Account was not in cookie or was invalid");
+            return null;
+        }
+
+        RefreshableKeycloakSecurityContext session = principal.getKeycloakSecurityContext();
+        if (session.isActive() && !session.getDeployment().isAlwaysRefreshToken()) return principal;
+        boolean success = session.refreshExpiredToken(false);
+        if (success && session.isActive()) return principal;
+
+        log.fine("Cleanup and expire cookie for user " + principal.getName() + " after failed refresh");
+        request.setUserPrincipal(null);
+        request.setAuthType(null);
+        CookieTokenStore.removeCookie(facade);
+        return null;
+    }
+}
diff --git a/integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/CatalinaRequestAuthenticator.java b/integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/CatalinaRequestAuthenticator.java
index 8516b1a..cc9e56a 100755
--- a/integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/CatalinaRequestAuthenticator.java
+++ b/integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/CatalinaRequestAuthenticator.java
@@ -1,20 +1,20 @@
 package org.keycloak.adapters.tomcat7;
 
-import org.apache.catalina.Session;
 import org.apache.catalina.authenticator.Constants;
 import org.apache.catalina.connector.Request;
-import org.apache.catalina.realm.GenericPrincipal;
 import org.keycloak.KeycloakPrincipal;
 import org.keycloak.KeycloakSecurityContext;
+import org.keycloak.adapters.AdapterTokenStore;
+import org.keycloak.adapters.AdapterUtils;
+import org.keycloak.adapters.KeycloakAccount;
 import org.keycloak.adapters.KeycloakDeployment;
 import org.keycloak.adapters.OAuthRequestAuthenticator;
 import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
 import org.keycloak.adapters.RequestAuthenticator;
-import org.keycloak.representations.AccessToken;
+import org.keycloak.enums.TokenStore;
 
 import java.io.IOException;
 import java.security.Principal;
-import java.util.Collections;
 import java.util.Set;
 import java.util.logging.Level;
 import java.util.logging.Logger;
@@ -28,16 +28,14 @@ import javax.servlet.http.HttpSession;
 public class CatalinaRequestAuthenticator extends RequestAuthenticator {
     private static final Logger log = Logger.getLogger(""+CatalinaRequestAuthenticator.class);
     protected KeycloakAuthenticatorValve valve;
-    protected CatalinaUserSessionManagement userSessionManagement;
     protected Request request;
 
     public CatalinaRequestAuthenticator(KeycloakDeployment deployment,
-                                        KeycloakAuthenticatorValve valve, CatalinaUserSessionManagement userSessionManagement,
+                                        KeycloakAuthenticatorValve valve, AdapterTokenStore tokenStore,
                                         CatalinaHttpFacade facade,
                                         Request request) {
-        super(facade, deployment, request.getConnector().getRedirectPort());
+        super(facade, deployment, tokenStore, request.getConnector().getRedirectPort());
         this.valve = valve;
-        this.userSessionManagement = userSessionManagement;
         this.request = request;
     }
 
@@ -47,7 +45,10 @@ public class CatalinaRequestAuthenticator extends RequestAuthenticator {
             @Override
             protected void saveRequest() {
                 try {
-                    valve.keycloakSaveRequest(request);
+                    // Support saving request just for TokenStore.SESSION TODO: Add to tokenStore spi?
+                    if (deployment.getTokenStore() == TokenStore.SESSION) {
+                        valve.keycloakSaveRequest(request);
+                    }
                 } catch (IOException e) {
                     throw new RuntimeException(e);
                 }
@@ -56,24 +57,36 @@ public class CatalinaRequestAuthenticator extends RequestAuthenticator {
     }
 
     @Override
-    protected void completeOAuthAuthentication(KeycloakPrincipal<RefreshableKeycloakSecurityContext> skp) {
-        RefreshableKeycloakSecurityContext securityContext = skp.getKeycloakSecurityContext();
+    protected void completeOAuthAuthentication(final KeycloakPrincipal<RefreshableKeycloakSecurityContext> skp) {
+        final RefreshableKeycloakSecurityContext securityContext = skp.getKeycloakSecurityContext();
+        final Set<String> roles = AdapterUtils.getRolesFromSecurityContext(securityContext);
+        KeycloakAccount account = new KeycloakAccount() {
+
+            @Override
+            public Principal getPrincipal() {
+                return skp;
+            }
+
+            @Override
+            public Set<String> getRoles() {
+                return roles;
+            }
+
+            @Override
+            public KeycloakSecurityContext getKeycloakSecurityContext() {
+                return securityContext;
+            }
+
+        };
+
         request.setAttribute(KeycloakSecurityContext.class.getName(), securityContext);
-    	Set<String> roles = getRolesFromToken(securityContext);
-        GenericPrincipal principal = new CatalinaSecurityContextHelper().createPrincipal(request.getContext().getRealm(), skp, roles, securityContext);
-        Session session = request.getSessionInternal(true);
-        session.setPrincipal(principal);
-        session.setAuthType("OAUTH");
-        session.setNote(KeycloakSecurityContext.class.getName(), securityContext);
-        String username = securityContext.getToken().getSubject();
-        log.finer("userSessionManagement.login: " + username);
-        userSessionManagement.login(session);
+        this.tokenStore.saveAccountInfo(account);
     }
 
     @Override
     protected void completeBearerAuthentication(KeycloakPrincipal<RefreshableKeycloakSecurityContext> principal) {
         RefreshableKeycloakSecurityContext securityContext = principal.getKeycloakSecurityContext();
-        Set<String> roles = getRolesFromToken(securityContext);
+        Set<String> roles = AdapterUtils.getRolesFromSecurityContext(securityContext);
         if (log.isLoggable(Level.FINE)) {
             log.fine("Completing bearer authentication. Bearer roles: " + roles);
         }
@@ -83,39 +96,6 @@ public class CatalinaRequestAuthenticator extends RequestAuthenticator {
         request.setAttribute(KeycloakSecurityContext.class.getName(), securityContext);
     }
 
-    protected Set<String> getRolesFromToken(RefreshableKeycloakSecurityContext session) {
-        Set<String> roles = null;
-        if (deployment.isUseResourceRoleMappings()) {
-            AccessToken.Access access = session.getToken().getResourceAccess(deployment.getResourceName());
-            if (access != null) roles = access.getRoles();
-        } else {
-            AccessToken.Access access =  session.getToken().getRealmAccess();
-            if (access != null) roles = access.getRoles();
-        }
-        if (roles == null) roles = Collections.emptySet();
-        return roles;
-    }
-
-    @Override
-    protected boolean isCached() {
-        if (request.getSessionInternal(false) == null || request.getSessionInternal().getPrincipal() == null)
-            return false;
-        log.finer("remote logged in already");
-        GenericPrincipal principal = (GenericPrincipal) request.getSessionInternal().getPrincipal();
-        request.setUserPrincipal(principal);
-        request.setAuthType("KEYCLOAK");
-        Session session = request.getSessionInternal();
-        if (session != null) {
-            RefreshableKeycloakSecurityContext securityContext = (RefreshableKeycloakSecurityContext) session.getNote(KeycloakSecurityContext.class.getName());
-            if (securityContext != null) {
-                securityContext.setDeployment(deployment);
-                request.setAttribute(KeycloakSecurityContext.class.getName(), securityContext);
-            }
-        }
-        restoreRequest();
-        return true;
-    }
-
     protected void restoreRequest() {
         if (request.getSessionInternal().getNote(Constants.FORM_REQUEST_NOTE) != null) {
             if (valve.keycloakRestoreRequest(request)) {
diff --git a/integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/CatalinaSessionTokenStore.java b/integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/CatalinaSessionTokenStore.java
new file mode 100644
index 0000000..81a765b
--- /dev/null
+++ b/integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/CatalinaSessionTokenStore.java
@@ -0,0 +1,108 @@
+package org.keycloak.adapters.tomcat7;
+
+import java.util.Set;
+import java.util.logging.Logger;
+
+import org.apache.catalina.Session;
+import org.apache.catalina.connector.Request;
+import org.apache.catalina.realm.GenericPrincipal;
+import org.keycloak.KeycloakSecurityContext;
+import org.keycloak.adapters.AdapterTokenStore;
+import org.keycloak.adapters.KeycloakAccount;
+import org.keycloak.adapters.KeycloakDeployment;
+import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
+import org.keycloak.adapters.RequestAuthenticator;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class CatalinaSessionTokenStore implements AdapterTokenStore {
+
+    private static final Logger log = Logger.getLogger(""+CatalinaSessionTokenStore.class);
+
+    private Request request;
+    private KeycloakDeployment deployment;
+    private CatalinaUserSessionManagement sessionManagement;
+
+    public CatalinaSessionTokenStore(Request request, KeycloakDeployment deployment, CatalinaUserSessionManagement sessionManagement) {
+        this.request = request;
+        this.deployment = deployment;
+        this.sessionManagement = sessionManagement;
+    }
+
+    @Override
+    public void checkCurrentToken() {
+        if (request.getSessionInternal(false) == null || request.getSessionInternal().getPrincipal() == null) return;
+        RefreshableKeycloakSecurityContext session = (RefreshableKeycloakSecurityContext) request.getSessionInternal().getNote(KeycloakSecurityContext.class.getName());
+        if (session == null) return;
+        // just in case session got serialized
+        if (session.getDeployment() == null) session.setCurrentRequestInfo(deployment, this);
+        if (session.isActive() && !session.getDeployment().isAlwaysRefreshToken()) return;
+
+        // FYI: A refresh requires same scope, so same roles will be set.  Otherwise, refresh will fail and token will
+        // not be updated
+        boolean success = session.refreshExpiredToken(false);
+        if (success && session.isActive()) return;
+
+        // Refresh failed, so user is already logged out from keycloak. Cleanup and expire our session
+        Session catalinaSession = request.getSessionInternal();
+        log.fine("Cleanup and expire session " + catalinaSession.getId() + " after failed refresh");
+        catalinaSession.removeNote(KeycloakSecurityContext.class.getName());
+        request.setUserPrincipal(null);
+        request.setAuthType(null);
+        catalinaSession.setPrincipal(null);
+        catalinaSession.setAuthType(null);
+        catalinaSession.expire();
+    }
+
+    @Override
+    public boolean isCached(RequestAuthenticator authenticator) {
+        if (request.getSessionInternal(false) == null || request.getSessionInternal().getPrincipal() == null)
+            return false;
+        log.fine("remote logged in already. Establish state from session");
+        GenericPrincipal principal = (GenericPrincipal) request.getSessionInternal().getPrincipal();
+        request.setUserPrincipal(principal);
+        request.setAuthType("KEYCLOAK");
+
+        RefreshableKeycloakSecurityContext securityContext = (RefreshableKeycloakSecurityContext) request.getSessionInternal().getNote(KeycloakSecurityContext.class.getName());
+        if (securityContext != null) {
+            securityContext.setCurrentRequestInfo(deployment, this);
+            request.setAttribute(KeycloakSecurityContext.class.getName(), securityContext);
+        }
+
+        ((CatalinaRequestAuthenticator)authenticator).restoreRequest();
+        return true;
+    }
+
+    @Override
+    public void saveAccountInfo(KeycloakAccount account) {
+        RefreshableKeycloakSecurityContext securityContext = (RefreshableKeycloakSecurityContext)account.getKeycloakSecurityContext();
+        Set<String> roles = account.getRoles();
+        GenericPrincipal principal = new CatalinaSecurityContextHelper().createPrincipal(request.getContext().getRealm(), account.getPrincipal(), roles, securityContext);
+
+        Session session = request.getSessionInternal(true);
+        session.setPrincipal(principal);
+        session.setAuthType("OAUTH");
+        session.setNote(KeycloakSecurityContext.class.getName(), securityContext);
+        String username = securityContext.getToken().getSubject();
+        log.fine("userSessionManagement.login: " + username);
+        this.sessionManagement.login(session);
+    }
+
+    @Override
+    public void logout() {
+        Session session = request.getSessionInternal(false);
+        if (session != null) {
+            session.removeNote(KeycloakSecurityContext.class.getName());
+            KeycloakSecurityContext ksc = (KeycloakSecurityContext)request.getAttribute(KeycloakSecurityContext.class.getName());
+            if (ksc instanceof RefreshableKeycloakSecurityContext) {
+                ((RefreshableKeycloakSecurityContext) ksc).logout(deployment);
+            }
+        }
+    }
+
+    @Override
+    public void refreshCallback(RefreshableKeycloakSecurityContext securityContext) {
+        // no-op
+    }
+}
diff --git a/integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/KeycloakAuthenticatorValve.java b/integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/KeycloakAuthenticatorValve.java
index 94447a1..0ffa42c 100755
--- a/integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/KeycloakAuthenticatorValve.java
+++ b/integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/KeycloakAuthenticatorValve.java
@@ -15,6 +15,7 @@ import org.apache.catalina.deploy.LoginConfig;
 import org.keycloak.KeycloakSecurityContext;
 import org.keycloak.adapters.AdapterConstants;
 import org.keycloak.adapters.AdapterDeploymentContext;
+import org.keycloak.adapters.AdapterTokenStore;
 import org.keycloak.adapters.AuthChallenge;
 import org.keycloak.adapters.AuthOutcome;
 import org.keycloak.adapters.HttpFacade;
@@ -24,6 +25,7 @@ import org.keycloak.adapters.NodesRegistrationManagement;
 import org.keycloak.adapters.PreAuthActionsHandler;
 import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
 import org.keycloak.adapters.ServerRequest;
+import org.keycloak.enums.TokenStore;
 
 import javax.servlet.ServletContext;
 import javax.servlet.ServletException;
@@ -45,6 +47,9 @@ import java.util.logging.Logger;
  * @version $Revision: 1 $
  */
 public class KeycloakAuthenticatorValve extends FormAuthenticator implements LifecycleListener {
+
+    public static final String TOKEN_STORE_NOTE = "TOKEN_STORE_NOTE";
+
 	private final static Logger log = Logger.getLogger(""+KeycloakAuthenticatorValve.class);
 	protected CatalinaUserSessionManagement userSessionManagement = new CatalinaUserSessionManagement();
     protected AdapterDeploymentContext deploymentContext;
@@ -70,16 +75,11 @@ public class KeycloakAuthenticatorValve extends FormAuthenticator implements Lif
         KeycloakSecurityContext ksc = (KeycloakSecurityContext)request.getAttribute(KeycloakSecurityContext.class.getName());
         if (ksc != null) {
             request.removeAttribute(KeycloakSecurityContext.class.getName());
-            Session session = request.getSessionInternal(false);
-            if (session != null) {
-                session.removeNote(KeycloakSecurityContext.class.getName());
-                try {
-                    CatalinaHttpFacade facade = new CatalinaHttpFacade(request, null);
-                    ServerRequest.invokeLogout(deploymentContext.resolveDeployment(facade), ksc.getToken().getSessionState());
-                } catch (Exception e) {
-                	log.severe("failed to invoke remote logout. " + e.getMessage());
-                }
-            }
+            CatalinaHttpFacade facade = new CatalinaHttpFacade(request, null);
+            KeycloakDeployment deployment = deploymentContext.resolveDeployment(facade);
+
+            AdapterTokenStore tokenStore = getTokenStore(request, facade, deployment);
+            tokenStore.logout();
         }
         super.logout(request);
     }
@@ -164,10 +164,11 @@ public class KeycloakAuthenticatorValve extends FormAuthenticator implements Lif
         if (deployment == null || !deployment.isConfigured()) {
             return false;
         }
+        AdapterTokenStore tokenStore = getTokenStore(request, facade, deployment);
 
         nodesRegistrationManagement.tryRegister(deployment);
 
-        CatalinaRequestAuthenticator authenticator = new CatalinaRequestAuthenticator(deployment, this, userSessionManagement, facade, request);
+        CatalinaRequestAuthenticator authenticator = new CatalinaRequestAuthenticator(deployment, this, tokenStore, facade, request);
         AuthOutcome outcome = authenticator.authenticate();
         if (outcome == AuthOutcome.AUTHENTICATED) {
             if (facade.isEnded()) {
@@ -188,27 +189,9 @@ public class KeycloakAuthenticatorValve extends FormAuthenticator implements Lif
      * @param request
      */
     protected void checkKeycloakSession(Request request, HttpFacade facade) {
-        if (request.getSessionInternal(false) == null || request.getPrincipal() == null) return;
-        RefreshableKeycloakSecurityContext session = (RefreshableKeycloakSecurityContext) request.getSessionInternal().getNote(KeycloakSecurityContext.class.getName());
-        if (session == null) return;
-        // just in case session got serialized
-        if (session.getDeployment() == null) session.setDeployment(deploymentContext.resolveDeployment(facade));
-        if (session.isActive() && !session.getDeployment().isAlwaysRefreshToken()) return;
-
-        // FYI: A refresh requires same scope, so same roles will be set.  Otherwise, refresh will fail and token will
-        // not be updated
-        boolean success = session.refreshExpiredToken(false);
-        if (success && session.isActive()) return;
-
-        // Refresh failed, so user is already logged out from keycloak. Cleanup and expire our session
-        Session catalinaSession = request.getSessionInternal();
-        log.fine("Cleanup and expire session " + catalinaSession + " after failed refresh");
-        catalinaSession.removeNote(KeycloakSecurityContext.class.getName());
-        request.setUserPrincipal(null);
-        request.setAuthType(null);
-        catalinaSession.setPrincipal(null);
-        catalinaSession.setAuthType(null);
-        catalinaSession.expire();
+        KeycloakDeployment deployment = deploymentContext.resolveDeployment(facade);
+        AdapterTokenStore tokenStore = getTokenStore(request, facade, deployment);
+        tokenStore.checkCurrentToken();
     }
 
     public void keycloakSaveRequest(Request request) throws IOException {
@@ -223,4 +206,20 @@ public class KeycloakAuthenticatorValve extends FormAuthenticator implements Lif
         }
     }
 
+    protected AdapterTokenStore getTokenStore(Request request, HttpFacade facade, KeycloakDeployment resolvedDeployment) {
+        AdapterTokenStore store = (AdapterTokenStore)request.getNote(TOKEN_STORE_NOTE);
+        if (store != null) {
+            return store;
+        }
+
+        if (resolvedDeployment.getTokenStore() == TokenStore.SESSION) {
+            store = new CatalinaSessionTokenStore(request, resolvedDeployment, userSessionManagement);
+        } else {
+            store = new CatalinaCookieTokenStore(request, facade, resolvedDeployment);
+        }
+
+        request.setNote(TOKEN_STORE_NOTE, store);
+        return store;
+    }
+
 }
diff --git a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/KeycloakUndertowAccount.java b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/KeycloakUndertowAccount.java
index 472bc90..82c2727 100755
--- a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/KeycloakUndertowAccount.java
+++ b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/KeycloakUndertowAccount.java
@@ -19,14 +19,15 @@ package org.keycloak.adapters.undertow;
 import io.undertow.security.idm.Account;
 import org.jboss.logging.Logger;
 import org.keycloak.KeycloakPrincipal;
+import org.keycloak.adapters.AdapterTokenStore;
+import org.keycloak.adapters.AdapterUtils;
+import org.keycloak.adapters.HttpFacade;
 import org.keycloak.adapters.KeycloakAccount;
 import org.keycloak.adapters.KeycloakDeployment;
 import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
-import org.keycloak.representations.AccessToken;
 
 import java.io.Serializable;
 import java.security.Principal;
-import java.util.Collections;
 import java.util.Set;
 
 /**
@@ -40,33 +41,11 @@ public class KeycloakUndertowAccount implements Account, Serializable, KeycloakA
 
     public KeycloakUndertowAccount(KeycloakPrincipal<RefreshableKeycloakSecurityContext> principal) {
         this.principal = principal;
-        setRoles(principal.getKeycloakSecurityContext().getToken());
+        setRoles(principal.getKeycloakSecurityContext());
     }
 
-    protected void setRoles(AccessToken accessToken) {
-        Set<String> roles = null;
-        RefreshableKeycloakSecurityContext session = getKeycloakSecurityContext();
-        if (session.getDeployment().isUseResourceRoleMappings()) {
-            if (log.isTraceEnabled()) {
-                log.trace("useResourceRoleMappings");
-            }
-            AccessToken.Access access = accessToken.getResourceAccess(session.getDeployment().getResourceName());
-            if (access != null) roles = access.getRoles();
-        } else {
-            if (log.isTraceEnabled()) {
-                log.trace("use realm role mappings");
-            }
-            AccessToken.Access access = accessToken.getRealmAccess();
-            if (access != null) roles = access.getRoles();
-        }
-        if (roles == null) roles = Collections.emptySet();
-        if (log.isTraceEnabled()) {
-            log.trace("Setting roles: ");
-            for (String role : roles) {
-                log.trace("   role: " + role);
-            }
-        }
-
+    protected void setRoles(RefreshableKeycloakSecurityContext session) {
+        Set<String> roles = AdapterUtils.getRolesFromSecurityContext(session);
         this.accountRoles = roles;
     }
 
@@ -85,11 +64,12 @@ public class KeycloakUndertowAccount implements Account, Serializable, KeycloakA
         return principal.getKeycloakSecurityContext();
     }
 
-    public void setDeployment(KeycloakDeployment deployment) {
-        principal.getKeycloakSecurityContext().setDeployment(deployment);
+    public void setCurrentRequestInfo(KeycloakDeployment deployment, AdapterTokenStore tokenStore) {
+        principal.getKeycloakSecurityContext().setCurrentRequestInfo(deployment, tokenStore);
     }
 
-    public boolean isActive() {
+    // Check if accessToken is active and try to refresh if it's not
+    public boolean checkActive() {
         // this object may have been serialized, so we need to reset realm config/metadata
         RefreshableKeycloakSecurityContext session = getKeycloakSecurityContext();
         if (session.isActive() && !session.getDeployment().isAlwaysRefreshToken()) {
@@ -106,7 +86,7 @@ public class KeycloakUndertowAccount implements Account, Serializable, KeycloakA
         }
         log.debug("refresh succeeded");
 
-        setRoles(session.getToken());
+        setRoles(session);
         return true;
     }
 
diff --git a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletKeycloakAuthMech.java b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletKeycloakAuthMech.java
index 7303bda..7f18e90 100755
--- a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletKeycloakAuthMech.java
+++ b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletKeycloakAuthMech.java
@@ -25,9 +25,12 @@ import io.undertow.servlet.handlers.ServletRequestContext;
 import org.jboss.logging.Logger;
 import org.keycloak.KeycloakSecurityContext;
 import org.keycloak.adapters.AdapterDeploymentContext;
+import org.keycloak.adapters.AdapterTokenStore;
+import org.keycloak.adapters.HttpFacade;
 import org.keycloak.adapters.KeycloakDeployment;
 import org.keycloak.adapters.NodesRegistrationManagement;
 import org.keycloak.adapters.RequestAuthenticator;
+import org.keycloak.enums.TokenStore;
 
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpSession;
@@ -40,13 +43,11 @@ import javax.servlet.http.HttpSession;
 public class ServletKeycloakAuthMech extends UndertowKeycloakAuthMech {
     private static final Logger log = Logger.getLogger(ServletKeycloakAuthMech.class);
 
-    protected UndertowUserSessionManagement userSessionManagement;
     protected NodesRegistrationManagement nodesRegistrationManagement;
     protected ConfidentialPortManager portManager;
 
     public ServletKeycloakAuthMech(AdapterDeploymentContext deploymentContext, UndertowUserSessionManagement userSessionManagement, NodesRegistrationManagement nodesRegistrationManagement, ConfidentialPortManager portManager) {
-        super(deploymentContext);
-        this.userSessionManagement = userSessionManagement;
+        super(deploymentContext, userSessionManagement);
         this.nodesRegistrationManagement = nodesRegistrationManagement;
         this.portManager = portManager;
     }
@@ -66,40 +67,12 @@ public class ServletKeycloakAuthMech extends UndertowKeycloakAuthMech {
         return keycloakAuthenticate(exchange, securityContext, authenticator);
     }
 
-    @Override
-    protected void registerNotifications(SecurityContext securityContext) {
-
-        final NotificationReceiver logoutReceiver = new NotificationReceiver() {
-            @Override
-            public void handleNotification(SecurityNotification notification) {
-                if (notification.getEventType() != SecurityNotification.EventType.LOGGED_OUT) return;
-                final ServletRequestContext servletRequestContext = notification.getExchange().getAttachment(ServletRequestContext.ATTACHMENT_KEY);
-                HttpServletRequest req = (HttpServletRequest) servletRequestContext.getServletRequest();
-                req.removeAttribute(KeycloakUndertowAccount.class.getName());
-                req.removeAttribute(KeycloakSecurityContext.class.getName());
-                HttpSession session = req.getSession(false);
-                if (session == null) return;
-                KeycloakUndertowAccount account = (KeycloakUndertowAccount)session.getAttribute(KeycloakUndertowAccount.class.getName());
-                if (account == null) return;
-                session.removeAttribute(KeycloakSecurityContext.class.getName());
-                session.removeAttribute(KeycloakUndertowAccount.class.getName());
-                if (account.getKeycloakSecurityContext() != null) {
-                    UndertowHttpFacade facade = new UndertowHttpFacade(notification.getExchange());
-                    account.getKeycloakSecurityContext().logout(deploymentContext.resolveDeployment(facade));
-                }
-            }
-        };
-
-        securityContext.registerNotificationReceiver(logoutReceiver);
-    }
-
-
-
     protected RequestAuthenticator createRequestAuthenticator(KeycloakDeployment deployment, HttpServerExchange exchange, SecurityContext securityContext, UndertowHttpFacade facade) {
 
         int confidentialPort = getConfidentilPort(exchange);
+        AdapterTokenStore tokenStore = getTokenStore(exchange, facade, deployment, securityContext);
         return new ServletRequestAuthenticator(facade, deployment,
-                confidentialPort, securityContext, exchange, userSessionManagement);
+                confidentialPort, securityContext, exchange, tokenStore);
     }
 
     protected int getConfidentilPort(HttpServerExchange exchange) {
@@ -112,4 +85,13 @@ public class ServletKeycloakAuthMech extends UndertowKeycloakAuthMech {
         return confidentialPort;
     }
 
+    @Override
+    protected AdapterTokenStore getTokenStore(HttpServerExchange exchange, HttpFacade facade, KeycloakDeployment deployment, SecurityContext securityContext) {
+        if (deployment.getTokenStore() == TokenStore.SESSION) {
+            return new ServletSessionTokenStore(exchange, deployment, sessionManagement, securityContext);
+        } else {
+            return new UndertowCookieTokenStore(facade, deployment, securityContext);
+        }
+    }
+
 }
diff --git a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletRequestAuthenticator.java b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletRequestAuthenticator.java
index 5416d3c..ed13f56 100755
--- a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletRequestAuthenticator.java
+++ b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletRequestAuthenticator.java
@@ -18,13 +18,11 @@ package org.keycloak.adapters.undertow;
 
 import io.undertow.security.api.SecurityContext;
 import io.undertow.server.HttpServerExchange;
-import io.undertow.server.session.Session;
 import io.undertow.servlet.handlers.ServletRequestContext;
-import io.undertow.util.Sessions;
 import org.keycloak.KeycloakPrincipal;
 import org.keycloak.KeycloakSecurityContext;
+import org.keycloak.adapters.AdapterTokenStore;
 import org.keycloak.adapters.HttpFacade;
-import org.keycloak.adapters.KeycloakAccount;
 import org.keycloak.adapters.KeycloakDeployment;
 import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
 
@@ -41,34 +39,8 @@ public class ServletRequestAuthenticator extends UndertowRequestAuthenticator {
 
     public ServletRequestAuthenticator(HttpFacade facade, KeycloakDeployment deployment, int sslRedirectPort,
                                        SecurityContext securityContext, HttpServerExchange exchange,
-                                       UndertowUserSessionManagement userSessionManagement) {
-        super(facade, deployment, sslRedirectPort, securityContext, exchange, userSessionManagement);
-    }
-
-    @Override
-    protected boolean isCached() {
-        HttpSession session = getSession(false);
-        if (session == null) {
-            log.debug("session was null, returning null");
-            return false;
-        }
-        KeycloakUndertowAccount account = (KeycloakUndertowAccount)session.getAttribute(KeycloakUndertowAccount.class.getName());
-        if (account == null) {
-            log.debug("Account was not in session, returning null");
-            return false;
-        }
-        account.setDeployment(deployment);
-        if (account.isActive()) {
-            log.debug("Cached account found");
-            securityContext.authenticationComplete(account, "KEYCLOAK", false);
-            propagateKeycloakContext( account);
-            return true;
-        } else {
-            log.debug("Refresh failed. Account was not active. Returning null and invalidating Http session");
-            session.setAttribute(KeycloakUndertowAccount.class.getName(), null);
-            session.invalidate();
-            return false;
-        }
+                                       AdapterTokenStore tokenStore) {
+        super(facade, deployment, sslRedirectPort, securityContext, exchange, tokenStore);
     }
 
     @Override
@@ -80,15 +52,6 @@ public class ServletRequestAuthenticator extends UndertowRequestAuthenticator {
     }
 
     @Override
-    protected void login(KeycloakAccount account) {
-        final ServletRequestContext servletRequestContext = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY);
-        HttpSession session = getSession(true);
-        session.setAttribute(KeycloakUndertowAccount.class.getName(), account);
-        userSessionManagement.login(servletRequestContext.getDeployment().getSessionManager());
-
-    }
-
-    @Override
     protected KeycloakUndertowAccount createAccount(KeycloakPrincipal<RefreshableKeycloakSecurityContext> principal) {
         return new KeycloakUndertowAccount(principal);
     }
diff --git a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletSessionTokenStore.java b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletSessionTokenStore.java
new file mode 100644
index 0000000..cbc7a0b
--- /dev/null
+++ b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletSessionTokenStore.java
@@ -0,0 +1,106 @@
+package org.keycloak.adapters.undertow;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpSession;
+
+import io.undertow.security.api.SecurityContext;
+import io.undertow.server.HttpServerExchange;
+import io.undertow.servlet.handlers.ServletRequestContext;
+import org.jboss.logging.Logger;
+import org.keycloak.KeycloakSecurityContext;
+import org.keycloak.adapters.AdapterTokenStore;
+import org.keycloak.adapters.KeycloakAccount;
+import org.keycloak.adapters.KeycloakDeployment;
+import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
+import org.keycloak.adapters.RequestAuthenticator;
+
+/**
+ * Per-request object. Storage of tokens in servlet session.
+ *
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class ServletSessionTokenStore implements AdapterTokenStore {
+
+    protected static Logger log = Logger.getLogger(ServletSessionTokenStore.class);
+
+    private final HttpServerExchange exchange;
+    private final KeycloakDeployment deployment;
+    private final UndertowUserSessionManagement sessionManagement;
+    private final SecurityContext securityContext;
+
+    public ServletSessionTokenStore(HttpServerExchange exchange, KeycloakDeployment deployment, UndertowUserSessionManagement sessionManagement,
+                                    SecurityContext securityContext) {
+        this.exchange = exchange;
+        this.deployment = deployment;
+        this.sessionManagement = sessionManagement;
+        this.securityContext = securityContext;
+    }
+
+    @Override
+    public void checkCurrentToken() {
+        // no-op on undertow
+    }
+
+    @Override
+    public boolean isCached(RequestAuthenticator authenticator) {
+        HttpSession session = getSession(false);
+        if (session == null) {
+            log.debug("session was null, returning null");
+            return false;
+        }
+        KeycloakUndertowAccount account = (KeycloakUndertowAccount)session.getAttribute(KeycloakUndertowAccount.class.getName());
+        if (account == null) {
+            log.debug("Account was not in session, returning null");
+            return false;
+        }
+        account.setCurrentRequestInfo(deployment, this);
+        if (account.checkActive()) {
+            log.debug("Cached account found");
+            securityContext.authenticationComplete(account, "KEYCLOAK", false);
+            ((UndertowRequestAuthenticator)authenticator).propagateKeycloakContext(account);
+            return true;
+        } else {
+            log.debug("Refresh failed. Account was not active. Returning null and invalidating Http session");
+            session.setAttribute(KeycloakUndertowAccount.class.getName(), null);
+            session.invalidate();
+            return false;
+        }
+    }
+
+    @Override
+    public void saveAccountInfo(KeycloakAccount account) {
+        final ServletRequestContext servletRequestContext = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY);
+        HttpSession session = getSession(true);
+        session.setAttribute(KeycloakUndertowAccount.class.getName(), account);
+        sessionManagement.login(servletRequestContext.getDeployment().getSessionManager());
+    }
+
+    @Override
+    public void logout() {
+        final ServletRequestContext servletRequestContext = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY);
+        HttpServletRequest req = (HttpServletRequest) servletRequestContext.getServletRequest();
+        req.removeAttribute(KeycloakUndertowAccount.class.getName());
+        req.removeAttribute(KeycloakSecurityContext.class.getName());
+        HttpSession session = req.getSession(false);
+        if (session == null) return;
+        KeycloakUndertowAccount account = (KeycloakUndertowAccount)session.getAttribute(KeycloakUndertowAccount.class.getName());
+        if (account == null) return;
+        session.removeAttribute(KeycloakSecurityContext.class.getName());
+        session.removeAttribute(KeycloakUndertowAccount.class.getName());
+        if (account.getKeycloakSecurityContext() != null) {
+            account.getKeycloakSecurityContext().logout(deployment);
+        }
+    }
+
+    @Override
+    public void refreshCallback(RefreshableKeycloakSecurityContext securityContext) {
+        // no-op
+    }
+
+    protected HttpSession getSession(boolean create) {
+        final ServletRequestContext servletRequestContext = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY);
+        HttpServletRequest req = (HttpServletRequest) servletRequestContext.getServletRequest();
+        return req.getSession(create);
+    }
+
+}
diff --git a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowCookieTokenStore.java b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowCookieTokenStore.java
new file mode 100644
index 0000000..5085919
--- /dev/null
+++ b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowCookieTokenStore.java
@@ -0,0 +1,79 @@
+package org.keycloak.adapters.undertow;
+
+import io.undertow.security.api.SecurityContext;
+import org.jboss.logging.Logger;
+import org.keycloak.KeycloakPrincipal;
+import org.keycloak.adapters.AdapterTokenStore;
+import org.keycloak.adapters.CookieTokenStore;
+import org.keycloak.adapters.HttpFacade;
+import org.keycloak.adapters.KeycloakAccount;
+import org.keycloak.adapters.KeycloakDeployment;
+import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
+import org.keycloak.adapters.RequestAuthenticator;
+
+/**
+ * Per-request object. Storage of tokens in cookie
+ *
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class UndertowCookieTokenStore implements AdapterTokenStore {
+
+    protected static Logger log = Logger.getLogger(UndertowCookieTokenStore.class);
+
+    private final HttpFacade facade;
+    private final KeycloakDeployment deployment;
+    private final SecurityContext securityContext;
+
+    public UndertowCookieTokenStore(HttpFacade facade, KeycloakDeployment deployment,
+                                    SecurityContext securityContext) {
+        this.facade = facade;
+        this.deployment = deployment;
+        this.securityContext = securityContext;
+    }
+
+    @Override
+    public void checkCurrentToken() {
+        // no-op on undertow
+    }
+
+    @Override
+    public boolean isCached(RequestAuthenticator authenticator) {
+        KeycloakPrincipal<RefreshableKeycloakSecurityContext> principal = CookieTokenStore.getPrincipalFromCookie(deployment, facade, this);
+        if (principal == null) {
+            log.debug("Account was not in cookie or was invalid, returning null");
+            return false;
+        }
+        KeycloakUndertowAccount account = new KeycloakUndertowAccount(principal);
+
+        if (account.checkActive()) {
+            log.debug("Cached account found");
+            securityContext.authenticationComplete(account, "KEYCLOAK", false);
+            ((UndertowRequestAuthenticator)authenticator).propagateKeycloakContext(account);
+            return true;
+        } else {
+            log.debug("Account was not active, removing cookie and returning false");
+            CookieTokenStore.removeCookie(facade);
+            return false;
+        }
+    }
+
+    @Override
+    public void saveAccountInfo(KeycloakAccount account) {
+        RefreshableKeycloakSecurityContext secContext = (RefreshableKeycloakSecurityContext)account.getKeycloakSecurityContext();
+        CookieTokenStore.setTokenCookie(deployment, facade, secContext);
+    }
+
+    @Override
+    public void logout() {
+        KeycloakPrincipal<RefreshableKeycloakSecurityContext> principal = CookieTokenStore.getPrincipalFromCookie(deployment, facade, this);
+        if (principal == null) return;
+
+        CookieTokenStore.removeCookie(facade);
+        principal.getKeycloakSecurityContext().logout(deployment);
+    }
+
+    @Override
+    public void refreshCallback(RefreshableKeycloakSecurityContext securityContext) {
+        CookieTokenStore.setTokenCookie(deployment, facade, securityContext);
+    }
+}
diff --git a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowKeycloakAuthMech.java b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowKeycloakAuthMech.java
index d1b9e4e..1c875cb 100755
--- a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowKeycloakAuthMech.java
+++ b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowKeycloakAuthMech.java
@@ -24,10 +24,17 @@ import io.undertow.server.HttpServerExchange;
 import io.undertow.server.session.Session;
 import io.undertow.util.AttachmentKey;
 import io.undertow.util.Sessions;
+import org.keycloak.KeycloakPrincipal;
 import org.keycloak.adapters.AdapterDeploymentContext;
+import org.keycloak.adapters.AdapterTokenStore;
 import org.keycloak.adapters.AuthChallenge;
 import org.keycloak.adapters.AuthOutcome;
+import org.keycloak.adapters.CookieTokenStore;
+import org.keycloak.adapters.HttpFacade;
+import org.keycloak.adapters.KeycloakDeployment;
+import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
 import org.keycloak.adapters.RequestAuthenticator;
+import org.keycloak.enums.TokenStore;
 
 /**
  * Abstract base class for a Keycloak-enabled Undertow AuthenticationMechanism.
@@ -37,9 +44,11 @@ import org.keycloak.adapters.RequestAuthenticator;
 public abstract class UndertowKeycloakAuthMech implements AuthenticationMechanism {
     public static final AttachmentKey<AuthChallenge> KEYCLOAK_CHALLENGE_ATTACHMENT_KEY = AttachmentKey.create(AuthChallenge.class);
     protected AdapterDeploymentContext deploymentContext;
+    protected UndertowUserSessionManagement sessionManagement;
 
-    public UndertowKeycloakAuthMech(AdapterDeploymentContext deploymentContext) {
+    public UndertowKeycloakAuthMech(AdapterDeploymentContext deploymentContext, UndertowUserSessionManagement sessionManagement) {
         this.deploymentContext = deploymentContext;
+        this.sessionManagement = sessionManagement;
     }
 
     @Override
@@ -54,21 +63,17 @@ public abstract class UndertowKeycloakAuthMech implements AuthenticationMechanis
         return new ChallengeResult(false);
     }
 
-    protected void registerNotifications(SecurityContext securityContext) {
+    protected void registerNotifications(final SecurityContext securityContext) {
 
         final NotificationReceiver logoutReceiver = new NotificationReceiver() {
             @Override
             public void handleNotification(SecurityNotification notification) {
                 if (notification.getEventType() != SecurityNotification.EventType.LOGGED_OUT) return;
-                Session session = Sessions.getSession(notification.getExchange());
-                if (session == null) return;
-                KeycloakUndertowAccount account = (KeycloakUndertowAccount)session.getAttribute(KeycloakUndertowAccount.class.getName());
-                if (account == null) return;
-                session.removeAttribute(KeycloakUndertowAccount.class.getName());
-                if (account.getKeycloakSecurityContext() != null) {
-                    UndertowHttpFacade facade = new UndertowHttpFacade(notification.getExchange());
-                    account.getKeycloakSecurityContext().logout(deploymentContext.resolveDeployment(facade));
-                }
+
+                UndertowHttpFacade facade = new UndertowHttpFacade(notification.getExchange());
+                KeycloakDeployment deployment = deploymentContext.resolveDeployment(facade);
+                AdapterTokenStore tokenStore = getTokenStore(notification.getExchange(), facade, deployment, securityContext);
+                tokenStore.logout();
             }
         };
 
@@ -96,4 +101,12 @@ public abstract class UndertowKeycloakAuthMech implements AuthenticationMechanis
         return AuthenticationMechanismOutcome.NOT_ATTEMPTED;
     }
 
+    protected AdapterTokenStore getTokenStore(HttpServerExchange exchange, HttpFacade facade, KeycloakDeployment deployment, SecurityContext securityContext) {
+        if (deployment.getTokenStore() == TokenStore.SESSION) {
+            return new UndertowSessionTokenStore(exchange, deployment, sessionManagement, securityContext);
+        } else {
+            return new UndertowCookieTokenStore(facade, deployment, securityContext);
+        }
+    }
+
 }
\ No newline at end of file
diff --git a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowRequestAuthenticator.java b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowRequestAuthenticator.java
index 671f2e7..6b9c351 100755
--- a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowRequestAuthenticator.java
+++ b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowRequestAuthenticator.java
@@ -21,8 +21,8 @@ import io.undertow.server.HttpServerExchange;
 import io.undertow.server.session.Session;
 import io.undertow.util.Sessions;
 import org.keycloak.KeycloakPrincipal;
+import org.keycloak.adapters.AdapterTokenStore;
 import org.keycloak.adapters.HttpFacade;
-import org.keycloak.adapters.KeycloakAccount;
 import org.keycloak.adapters.KeycloakDeployment;
 import org.keycloak.adapters.OAuthRequestAuthenticator;
 import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
@@ -36,16 +36,14 @@ import org.keycloak.adapters.RequestAuthenticator;
 public abstract class UndertowRequestAuthenticator extends RequestAuthenticator {
     protected SecurityContext securityContext;
     protected HttpServerExchange exchange;
-    protected UndertowUserSessionManagement userSessionManagement;
 
 
     public UndertowRequestAuthenticator(HttpFacade facade, KeycloakDeployment deployment, int sslRedirectPort,
                                         SecurityContext securityContext, HttpServerExchange exchange,
-                                        UndertowUserSessionManagement userSessionManagement) {
-        super(facade, deployment, sslRedirectPort);
+                                        AdapterTokenStore tokenStore) {
+        super(facade, deployment, tokenStore, sslRedirectPort);
         this.securityContext = securityContext;
         this.exchange = exchange;
-        this.userSessionManagement = userSessionManagement;
     }
 
     protected void propagateKeycloakContext(KeycloakUndertowAccount account) {
@@ -67,16 +65,9 @@ public abstract class UndertowRequestAuthenticator extends RequestAuthenticator 
         KeycloakUndertowAccount account = createAccount(principal);
         securityContext.authenticationComplete(account, "KEYCLOAK", false);
         propagateKeycloakContext(account);
-        login(account);
+        tokenStore.saveAccountInfo(account);
     }
 
-    protected void login(KeycloakAccount account) {
-        Session session = Sessions.getOrCreateSession(exchange);
-        session.setAttribute(KeycloakUndertowAccount.class.getName(), account);
-        userSessionManagement.login(session.getSessionManager());
-    }
-
-
     @Override
     protected void completeBearerAuthentication(KeycloakPrincipal<RefreshableKeycloakSecurityContext> principal) {
         KeycloakUndertowAccount account = createAccount(principal);
@@ -85,32 +76,6 @@ public abstract class UndertowRequestAuthenticator extends RequestAuthenticator 
     }
 
     @Override
-    protected boolean isCached() {
-        Session session = Sessions.getSession(exchange);
-        if (session == null) {
-            log.debug("session was null, returning null");
-            return false;
-        }
-        KeycloakUndertowAccount account = (KeycloakUndertowAccount)session.getAttribute(KeycloakUndertowAccount.class.getName());
-        if (account == null) {
-            log.debug("Account was not in session, returning null");
-            return false;
-        }
-        account.setDeployment(deployment);
-        if (account.isActive()) {
-            log.debug("Cached account found");
-            securityContext.authenticationComplete(account, "KEYCLOAK", false);
-            propagateKeycloakContext( account);
-            return true;
-        } else {
-            log.debug("Account was not active, returning false");
-            session.removeAttribute(KeycloakUndertowAccount.class.getName());
-            session.invalidate(exchange);
-            return false;
-        }
-    }
-
-    @Override
     protected String getHttpSessionId(boolean create) {
         if (create) {
             Session session = Sessions.getOrCreateSession(exchange);
diff --git a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowSessionTokenStore.java b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowSessionTokenStore.java
new file mode 100644
index 0000000..9284884
--- /dev/null
+++ b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowSessionTokenStore.java
@@ -0,0 +1,90 @@
+package org.keycloak.adapters.undertow;
+
+import io.undertow.security.api.SecurityContext;
+import io.undertow.server.HttpServerExchange;
+import io.undertow.server.session.Session;
+import io.undertow.util.Sessions;
+import org.jboss.logging.Logger;
+import org.keycloak.adapters.AdapterTokenStore;
+import org.keycloak.adapters.KeycloakAccount;
+import org.keycloak.adapters.KeycloakDeployment;
+import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
+import org.keycloak.adapters.RequestAuthenticator;
+
+/**
+ * Per-request object. Storage of tokens in undertow session.
+ *
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class UndertowSessionTokenStore implements AdapterTokenStore {
+
+    protected static Logger log = Logger.getLogger(UndertowSessionTokenStore.class);
+
+    private final HttpServerExchange exchange;
+    private final KeycloakDeployment deployment;
+    private final UndertowUserSessionManagement sessionManagement;
+    private final SecurityContext securityContext;
+
+    public UndertowSessionTokenStore(HttpServerExchange exchange, KeycloakDeployment deployment, UndertowUserSessionManagement sessionManagement,
+                                     SecurityContext securityContext) {
+        this.exchange = exchange;
+        this.deployment = deployment;
+        this.sessionManagement = sessionManagement;
+        this.securityContext = securityContext;
+    }
+
+    @Override
+    public void checkCurrentToken() {
+        // no-op on undertow
+    }
+
+    @Override
+    public boolean isCached(RequestAuthenticator authenticator) {
+        Session session = Sessions.getSession(exchange);
+        if (session == null) {
+            log.debug("session was null, returning null");
+            return false;
+        }
+        KeycloakUndertowAccount account = (KeycloakUndertowAccount)session.getAttribute(KeycloakUndertowAccount.class.getName());
+        if (account == null) {
+            log.debug("Account was not in session, returning null");
+            return false;
+        }
+        account.setCurrentRequestInfo(deployment, this);
+        if (account.checkActive()) {
+            log.debug("Cached account found");
+            securityContext.authenticationComplete(account, "KEYCLOAK", false);
+            ((UndertowRequestAuthenticator)authenticator).propagateKeycloakContext(account);
+            return true;
+        } else {
+            log.debug("Account was not active, returning false");
+            session.removeAttribute(KeycloakUndertowAccount.class.getName());
+            session.invalidate(exchange);
+            return false;
+        }
+    }
+
+    @Override
+    public void saveAccountInfo(KeycloakAccount account) {
+        Session session = Sessions.getOrCreateSession(exchange);
+        session.setAttribute(KeycloakUndertowAccount.class.getName(), account);
+        sessionManagement.login(session.getSessionManager());
+    }
+
+    @Override
+    public void logout() {
+        Session session = Sessions.getSession(exchange);
+        if (session == null) return;
+        KeycloakUndertowAccount account = (KeycloakUndertowAccount)session.getAttribute(KeycloakUndertowAccount.class.getName());
+        if (account == null) return;
+        session.removeAttribute(KeycloakUndertowAccount.class.getName());
+        if (account.getKeycloakSecurityContext() != null) {
+            account.getKeycloakSecurityContext().logout(deployment);
+        }
+    }
+
+    @Override
+    public void refreshCallback(RefreshableKeycloakSecurityContext securityContext) {
+        // no-op
+    }
+}
diff --git a/integration/wildfly-adapter/src/main/java/org/keycloak/adapters/wildfly/WildflyAuthenticationMechanism.java b/integration/wildfly-adapter/src/main/java/org/keycloak/adapters/wildfly/WildflyAuthenticationMechanism.java
index cdb5315..6880d66 100755
--- a/integration/wildfly-adapter/src/main/java/org/keycloak/adapters/wildfly/WildflyAuthenticationMechanism.java
+++ b/integration/wildfly-adapter/src/main/java/org/keycloak/adapters/wildfly/WildflyAuthenticationMechanism.java
@@ -4,6 +4,7 @@ import io.undertow.security.api.SecurityContext;
 import io.undertow.server.HttpServerExchange;
 import io.undertow.servlet.api.ConfidentialPortManager;
 import org.keycloak.adapters.AdapterDeploymentContext;
+import org.keycloak.adapters.AdapterTokenStore;
 import org.keycloak.adapters.KeycloakDeployment;
 import org.keycloak.adapters.NodesRegistrationManagement;
 import org.keycloak.adapters.undertow.ServletKeycloakAuthMech;
@@ -27,7 +28,8 @@ public class WildflyAuthenticationMechanism extends ServletKeycloakAuthMech {
     @Override
     protected ServletRequestAuthenticator createRequestAuthenticator(KeycloakDeployment deployment, HttpServerExchange exchange, SecurityContext securityContext, UndertowHttpFacade facade) {
         int confidentialPort = getConfidentilPort(exchange);
+        AdapterTokenStore tokenStore = getTokenStore(exchange, facade, deployment, securityContext);
         return new WildflyRequestAuthenticator(facade, deployment,
-                confidentialPort, securityContext, exchange, userSessionManagement);
+                confidentialPort, securityContext, exchange, tokenStore);
     }
 }
diff --git a/integration/wildfly-adapter/src/main/java/org/keycloak/adapters/wildfly/WildflyRequestAuthenticator.java b/integration/wildfly-adapter/src/main/java/org/keycloak/adapters/wildfly/WildflyRequestAuthenticator.java
index bc8a6de..84074d6 100755
--- a/integration/wildfly-adapter/src/main/java/org/keycloak/adapters/wildfly/WildflyRequestAuthenticator.java
+++ b/integration/wildfly-adapter/src/main/java/org/keycloak/adapters/wildfly/WildflyRequestAuthenticator.java
@@ -8,6 +8,7 @@ import org.jboss.security.SecurityConstants;
 import org.jboss.security.SecurityContextAssociation;
 import org.jboss.security.SimpleGroup;
 import org.jboss.security.SimplePrincipal;
+import org.keycloak.adapters.AdapterTokenStore;
 import org.keycloak.adapters.HttpFacade;
 import org.keycloak.adapters.KeycloakDeployment;
 import org.keycloak.adapters.undertow.KeycloakUndertowAccount;
@@ -31,8 +32,8 @@ public class WildflyRequestAuthenticator extends ServletRequestAuthenticator {
 
     public WildflyRequestAuthenticator(HttpFacade facade, KeycloakDeployment deployment, int sslRedirectPort,
                                        SecurityContext securityContext, HttpServerExchange exchange,
-                                       UndertowUserSessionManagement userSessionManagement) {
-        super(facade, deployment, sslRedirectPort, securityContext, exchange, userSessionManagement);
+                                       AdapterTokenStore tokenStore) {
+        super(facade, deployment, sslRedirectPort, securityContext, exchange, tokenStore);
     }
 
     @Override
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTest.java
index 5443a7e..cca4389 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTest.java
@@ -32,7 +32,6 @@ import org.keycloak.adapters.AdapterConstants;
 import org.keycloak.models.ApplicationModel;
 import org.keycloak.models.Constants;
 import org.keycloak.models.KeycloakSession;
-import org.keycloak.models.PasswordPolicy;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.UserModel;
 import org.keycloak.models.UserSessionModel;
@@ -112,7 +111,7 @@ public class AdapterTest {
             TokenManager tm = new TokenManager();
             UserModel admin = session.users().getUserByUsername("admin", adminRealm);
             UserSessionModel userSession = session.sessions().createUserSession(adminRealm, admin, "admin", null, "form", false);
-            AccessToken token = tm.createClientAccessToken(tm.getAccess(null, adminConsole, admin), adminRealm, adminConsole, admin, userSession);
+            AccessToken token = tm.createClientAccessToken(TokenManager.getAccess(null, adminConsole, admin), adminRealm, adminConsole, admin, userSession);
             return tm.encodeToken(adminRealm, token);
         } finally {
             keycloakRule.stopSession(session, true);
@@ -440,7 +439,7 @@ public class AdapterTest {
         // Open browser2
         browser2.webRule.before();
         try {
-            browser2.loginAndCheckSession(browser2.driver, browser2.loginPage);
+            loginAndCheckSession(browser2.driver, browser2.loginPage);
 
             // Logout in browser1
             String logoutUri = OpenIDConnectService.logoutUrl(UriBuilder.fromUri("http://localhost:8081/auth"))
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/CookieTokenStoreAdapterTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/CookieTokenStoreAdapterTest.java
new file mode 100644
index 0000000..e76d892
--- /dev/null
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/CookieTokenStoreAdapterTest.java
@@ -0,0 +1,180 @@
+package org.keycloak.testsuite.adapter;
+
+import java.net.URL;
+
+import javax.ws.rs.core.UriBuilder;
+
+import org.junit.Assert;
+import org.junit.ClassRule;
+import org.junit.Rule;
+import org.junit.Test;
+import org.keycloak.adapters.AdapterConstants;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.protocol.oidc.OpenIDConnectService;
+import org.keycloak.representations.idm.RealmRepresentation;
+import org.keycloak.services.managers.RealmManager;
+import org.keycloak.testsuite.OAuthClient;
+import org.keycloak.testsuite.pages.LoginPage;
+import org.keycloak.testsuite.rule.AbstractKeycloakRule;
+import org.keycloak.testsuite.rule.WebResource;
+import org.keycloak.testsuite.rule.WebRule;
+import org.keycloak.testutils.KeycloakServer;
+import org.openqa.selenium.Cookie;
+import org.openqa.selenium.WebDriver;
+
+/**
+ * KEYCLOAK-702
+ *
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class CookieTokenStoreAdapterTest {
+
+    public static final String LOGIN_URL = OpenIDConnectService.loginPageUrl(UriBuilder.fromUri("http://localhost:8081/auth")).build("demo").toString();
+
+    @ClassRule
+    public static AbstractKeycloakRule keycloakRule = new AbstractKeycloakRule() {
+
+        @Override
+        protected void configure(KeycloakSession session, RealmManager manager, RealmModel adminRealm) {
+            RealmRepresentation representation = KeycloakServer.loadJson(getClass().getResourceAsStream("/adapter-test/demorealm.json"), RealmRepresentation.class);
+            manager.importRealm(representation);
+
+            URL url = getClass().getResource("/adapter-test/cust-app-keycloak.json");
+            deployApplication("customer-portal", "/customer-portal", CustomerServlet.class, url.getPath(), "user");
+            url = getClass().getResource("/adapter-test/cust-app-cookie-keycloak.json");
+            deployApplication("customer-cookie-portal", "/customer-cookie-portal", CustomerServlet.class, url.getPath(), "user");
+            url = getClass().getResource("/adapter-test/customer-db-keycloak.json");
+            deployApplication("customer-db", "/customer-db", CustomerDatabaseServlet.class, url.getPath(), "user");
+        }
+    };
+
+    @Rule
+    public WebRule webRule = new WebRule(this);
+
+    @WebResource
+    protected WebDriver driver;
+
+    @WebResource
+    protected OAuthClient oauth;
+
+    @WebResource
+    protected LoginPage loginPage;
+
+    @Test
+    public void testTokenInCookieSSO() throws Throwable {
+        // Login
+        String tokenCookie = loginToCustomerCookiePortal();
+
+        // SSO to second app
+        driver.navigate().to("http://localhost:8081/customer-portal");
+        assertLogged();
+
+        // return to customer-cookie-portal and assert still same cookie (accessToken didn't expire)
+        driver.navigate().to("http://localhost:8081/customer-cookie-portal");
+        assertLogged();
+        String tokenCookie2 = driver.manage().getCookieNamed(AdapterConstants.KEYCLOAK_ADAPTER_STATE_COOKIE).getValue();
+        Assert.assertEquals(tokenCookie, tokenCookie2);
+
+        // Logout with httpServletRequest
+        logoutFromCustomerCookiePortal();
+
+        // Also should be logged-out from the second app
+        driver.navigate().to("http://localhost:8081/customer-portal");
+        Assert.assertTrue(driver.getCurrentUrl().startsWith(LOGIN_URL));
+    }
+
+    @Test
+    public void testTokenInCookieRefresh() throws Throwable {
+        // Set token timeout 1 sec
+        KeycloakSession session = keycloakRule.startSession();
+        RealmModel realm = session.realms().getRealmByName("demo");
+        int originalTokenTimeout = realm.getAccessTokenLifespan();
+        realm.setAccessTokenLifespan(1);
+        session.getTransaction().commit();
+        session.close();
+
+        // login to customer-cookie-portal
+        String tokenCookie1 = loginToCustomerCookiePortal();
+
+        // wait 2 secs
+        Thread.sleep(2000);
+
+        // assert cookie was refreshed
+        driver.navigate().to("http://localhost:8081/customer-cookie-portal");
+        Assert.assertEquals(driver.getCurrentUrl(), "http://localhost:8081/customer-cookie-portal");
+        assertLogged();
+        String tokenCookie2 = driver.manage().getCookieNamed(AdapterConstants.KEYCLOAK_ADAPTER_STATE_COOKIE).getValue();
+        Assert.assertNotEquals(tokenCookie1, tokenCookie2);
+
+        // login to 2nd app and logout from it
+        driver.navigate().to("http://localhost:8081/customer-portal");
+        Assert.assertEquals(driver.getCurrentUrl(), "http://localhost:8081/customer-portal");
+        assertLogged();
+
+        driver.navigate().to("http://localhost:8081/customer-portal/logout");
+        driver.navigate().to("http://localhost:8081/customer-portal");
+        Assert.assertTrue(driver.getCurrentUrl().startsWith(LOGIN_URL));
+
+        // wait 2 secs until accessToken expires for customer-cookie-portal too.
+        Thread.sleep(2000);
+
+        // assert not logged in customer-cookie-portal
+        driver.navigate().to("http://localhost:8081/customer-cookie-portal");
+        Assert.assertTrue(driver.getCurrentUrl().startsWith(LOGIN_URL));
+
+        // Change timeout back
+        session = keycloakRule.startSession();
+        realm = session.realms().getRealmByName("demo");
+        realm.setAccessTokenLifespan(originalTokenTimeout);
+        session.getTransaction().commit();
+        session.close();
+    }
+
+    @Test
+    public void testInvalidTokenCookie() throws Throwable {
+        // Login
+        String tokenCookie = loginToCustomerCookiePortal();
+        String changedTokenCookie = tokenCookie.replace("a", "b");
+
+        // change cookie to invalid value
+        driver.manage().addCookie(new Cookie(AdapterConstants.KEYCLOAK_ADAPTER_STATE_COOKIE, changedTokenCookie, "/customer-cookie-portal"));
+
+        // visit page and assert re-logged and cookie was refreshed
+        driver.navigate().to("http://localhost:8081/customer-cookie-portal");
+        Assert.assertEquals(driver.getCurrentUrl(), "http://localhost:8081/customer-cookie-portal");
+        String currentCookie = driver.manage().getCookieNamed(AdapterConstants.KEYCLOAK_ADAPTER_STATE_COOKIE).getValue();
+        Assert.assertNotEquals(currentCookie, tokenCookie);
+        Assert.assertNotEquals(currentCookie, changedTokenCookie);
+
+        // logout
+        logoutFromCustomerCookiePortal();
+    }
+
+    // login to customer-cookie-portal and return the KEYCLOAK_ADAPTER_STATE cookie established on adapter
+    private String loginToCustomerCookiePortal() {
+        driver.navigate().to("http://localhost:8081/customer-cookie-portal");
+        Assert.assertTrue(driver.getCurrentUrl().startsWith(LOGIN_URL));
+        loginPage.login("bburke@redhat.com", "password");
+        Assert.assertEquals(driver.getCurrentUrl(), "http://localhost:8081/customer-cookie-portal");
+        assertLogged();
+
+        // Assert no JSESSIONID cookie
+        Assert.assertNull(driver.manage().getCookieNamed("JSESSIONID"));
+
+        return driver.manage().getCookieNamed(AdapterConstants.KEYCLOAK_ADAPTER_STATE_COOKIE).getValue();
+    }
+
+    private void logoutFromCustomerCookiePortal() {
+        driver.navigate().to("http://localhost:8081/customer-cookie-portal/logout");
+        Assert.assertTrue(driver.getPageSource().contains("ok"));
+        Assert.assertNull(driver.manage().getCookieNamed(AdapterConstants.KEYCLOAK_ADAPTER_STATE_COOKIE));
+        driver.navigate().to("http://localhost:8081/customer-cookie-portal");
+        Assert.assertTrue(driver.getCurrentUrl().startsWith(LOGIN_URL));
+    }
+
+    private void assertLogged() {
+        String pageSource = driver.getPageSource();
+        Assert.assertTrue(pageSource.contains("Bill Burke") && pageSource.contains("Stian Thorgersen"));
+    }
+}
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/CustomerServlet.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/CustomerServlet.java
index eafe55b..a32bc17 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/CustomerServlet.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/CustomerServlet.java
@@ -29,8 +29,10 @@ public class CustomerServlet extends HttpServlet {
         if (req.getRequestURI().toString().endsWith("logout")) {
             resp.setStatus(200);
             pw.println("ok");
-            pw.flush();
+
+            // Call logout before pw.flush
             req.logout();
+            pw.flush();
             return;
         }
         KeycloakSecurityContext context = (KeycloakSecurityContext)req.getAttribute(KeycloakSecurityContext.class.getName());
diff --git a/testsuite/integration/src/test/resources/adapter-test/cust-app-cookie-keycloak.json b/testsuite/integration/src/test/resources/adapter-test/cust-app-cookie-keycloak.json
new file mode 100644
index 0000000..92fe860
--- /dev/null
+++ b/testsuite/integration/src/test/resources/adapter-test/cust-app-cookie-keycloak.json
@@ -0,0 +1,12 @@
+{
+    "realm": "demo",
+    "resource": "customer-cookie-portal",
+    "realm-public-key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
+    "auth-server-url": "http://localhost:8081/auth",
+    "ssl-required" : "external",
+    "expose-token": true,
+    "token-store": "cookie",
+    "credentials": {
+        "secret": "password"
+    }
+}
\ No newline at end of file
diff --git a/testsuite/integration/src/test/resources/adapter-test/demorealm.json b/testsuite/integration/src/test/resources/adapter-test/demorealm.json
index fc6ebae..9a5da24 100755
--- a/testsuite/integration/src/test/resources/adapter-test/demorealm.json
+++ b/testsuite/integration/src/test/resources/adapter-test/demorealm.json
@@ -69,6 +69,15 @@
             "secret": "password"
         },
         {
+            "name": "customer-cookie-portal",
+            "enabled": true,
+            "baseUrl": "http://localhost:8081/customer-cookie-portal",
+            "redirectUris": [
+                "http://localhost:8081/customer-cookie-portal/*"
+            ],
+            "secret": "password"
+        },
+        {
             "name": "customer-portal-js",
             "enabled": true,
             "publicClient": true,