keycloak-aplcache

Changes

integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowKeycloakSession.java 43(+0 -43)

Details

diff --git a/admin-ui/src/main/resources/META-INF/resources/admin/js/controllers/realm.js b/admin-ui/src/main/resources/META-INF/resources/admin/js/controllers/realm.js
index 9c5e974..f1a4f49 100755
--- a/admin-ui/src/main/resources/META-INF/resources/admin/js/controllers/realm.js
+++ b/admin-ui/src/main/resources/META-INF/resources/admin/js/controllers/realm.js
@@ -549,7 +549,7 @@ module.controller('RealmTokenDetailCtrl', function($scope, Realm, realm, $http, 
         $scope.realm.accessTokenLifespan = TimeUnit.convert($scope.realm.accessTokenLifespan, from, to);
     });
 
-    $scope.realm.centralLoginLifespanUnit = TimeUnit.autoUnit(realm.accessTokenLifespan);
+    $scope.realm.centralLoginLifespanUnit = TimeUnit.autoUnit(realm.centralLoginLifespan);
     $scope.realm.centralLoginLifespan = TimeUnit.toUnit(realm.centralLoginLifespan, $scope.realm.centralLoginLifespanUnit);
     $scope.$watch('realm.centralLoginLifespanUnit', function(to, from) {
         $scope.realm.centralLoginLifespan = TimeUnit.convert($scope.realm.centralLoginLifespan, from, to);
@@ -592,6 +592,7 @@ module.controller('RealmTokenDetailCtrl', function($scope, Realm, realm, $http, 
         delete realmCopy["accessCodeLifespanUserActionUnit"];
 
         realmCopy.accessTokenLifespan = TimeUnit.toSeconds($scope.realm.accessTokenLifespan, $scope.realm.accessTokenLifespanUnit)
+        realmCopy.centralLoginLifespan = TimeUnit.toSeconds($scope.realm.centralLoginLifespan, $scope.realm.centralLoginLifespanUnit)
         realmCopy.refreshTokenLifespan = TimeUnit.toSeconds($scope.realm.refreshTokenLifespan, $scope.realm.refreshTokenLifespanUnit)
         realmCopy.accessCodeLifespan = TimeUnit.toSeconds($scope.realm.accessCodeLifespan, $scope.realm.accessCodeLifespanUnit)
         realmCopy.accessCodeLifespanUserAction = TimeUnit.toSeconds($scope.realm.accessCodeLifespanUserAction, $scope.realm.accessCodeLifespanUserActionUnit)
diff --git a/core/src/main/java/org/keycloak/KeycloakAuthenticatedSession.java b/core/src/main/java/org/keycloak/KeycloakAuthenticatedSession.java
index 33c87d9..658e19a 100755
--- a/core/src/main/java/org/keycloak/KeycloakAuthenticatedSession.java
+++ b/core/src/main/java/org/keycloak/KeycloakAuthenticatedSession.java
@@ -35,4 +35,7 @@ public class KeycloakAuthenticatedSession implements Serializable {
         return metadata;
     }
 
+    public void setMetadata(ResourceMetadata metadata) {
+        this.metadata = metadata;
+    }
 }
diff --git a/core/src/main/java/org/keycloak/KeycloakPrincipal.java b/core/src/main/java/org/keycloak/KeycloakPrincipal.java
index 07a9322..e4aab9c 100755
--- a/core/src/main/java/org/keycloak/KeycloakPrincipal.java
+++ b/core/src/main/java/org/keycloak/KeycloakPrincipal.java
@@ -1,12 +1,13 @@
 package org.keycloak;
 
+import java.io.Serializable;
 import java.security.Principal;
 
 /**
  * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
  * @version $Revision: 1 $
  */
-public class KeycloakPrincipal implements Principal {
+public class KeycloakPrincipal implements Principal, Serializable {
     protected String name;
     protected String surrogate;
 
diff --git a/distribution/modules/src/main/resources/modules/org/keycloak/keycloak-adapter-core/main/module.xml b/distribution/modules/src/main/resources/modules/org/keycloak/keycloak-adapter-core/main/module.xml
index 0aa0a29..5323ea3 100755
--- a/distribution/modules/src/main/resources/modules/org/keycloak/keycloak-adapter-core/main/module.xml
+++ b/distribution/modules/src/main/resources/modules/org/keycloak/keycloak-adapter-core/main/module.xml
@@ -32,6 +32,7 @@
         <module name="org.codehaus.jackson.jackson-mapper-asl"/>
         <module name="org.codehaus.jackson.jackson-xc"/>
         <module name="org.apache.httpcomponents" />
+        <module name="org.jboss.logging"/>
         <module name="org.keycloak.keycloak-core"/>
     </dependencies>
 
diff --git a/forms/common-themes/src/main/resources/theme/login/base/login.ftl b/forms/common-themes/src/main/resources/theme/login/base/login.ftl
index 4fd6f63..b609784 100755
--- a/forms/common-themes/src/main/resources/theme/login/base/login.ftl
+++ b/forms/common-themes/src/main/resources/theme/login/base/login.ftl
@@ -28,6 +28,13 @@
 
             <div class="${properties.kcFormGroupClass!}">
                 <div id="kc-form-options" class="${properties.kcFormOptionsClass!}">
+                    <#if realm.rememberMe>
+                        <div class="checkbox">
+                            <label>
+                                <input id="rememberMe" name="rememberMe" type="checkbox" tabindex="3"> Remember Me
+                            </label>
+                        </div>
+                    </#if>
                     <div class="${properties.kcFormOptionsWrapperClass!}">
                         <#if realm.registrationAllowed>
                             <span>${rb.noAccount} <a href="${url.registrationUrl}">${rb.register}</a></span>
@@ -43,7 +50,7 @@
                         <input class="btn btn-primary btn-lg" name="login" id="kc-login" type="submit" value="${rb.logIn}"/>
                         <input class="btn btn-default btn-lg" name="cancel" id="kc-cancel" type="submit" value="${rb.cancel}"/>
                     </div>
-                </div>
+                 </div>
             </div>
         </form>
     <#elseif section = "info" >
diff --git a/forms/common-themes/src/main/resources/theme/login/base/messages/messages.properties b/forms/common-themes/src/main/resources/theme/login/base/messages/messages.properties
old mode 100644
new mode 100755
index 79c8e7b..9aa907e
--- a/forms/common-themes/src/main/resources/theme/login/base/messages/messages.properties
+++ b/forms/common-themes/src/main/resources/theme/login/base/messages/messages.properties
@@ -16,6 +16,7 @@ firstName=First name
 lastName=Last name
 email=Email
 password=Password
+rememberMe=Remember me
 passwordConfirm=Confirm password
 passwordNew=New Password
 passwordNewConfirm=New Password confirmation
diff --git a/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/model/RealmBean.java b/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/model/RealmBean.java
index 8a71244..a6b52f0 100755
--- a/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/model/RealmBean.java
+++ b/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/model/RealmBean.java
@@ -49,5 +49,9 @@ public class RealmBean {
     public boolean isResetPasswordAllowed() {
         return realm.isResetPasswordAllowed();
     }
+
+    public boolean isRememberMe() {
+        return realm.isRememberMe();
+    }
     
 }
diff --git a/integration/adapter-core/pom.xml b/integration/adapter-core/pom.xml
index be1e49a..ae0fc81 100755
--- a/integration/adapter-core/pom.xml
+++ b/integration/adapter-core/pom.xml
@@ -14,6 +14,12 @@
 
     <dependencies>
         <dependency>
+            <groupId>org.jboss.logging</groupId>
+            <artifactId>jboss-logging</artifactId>
+            <version>3.1.2.GA</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
             <groupId>org.keycloak</groupId>
             <artifactId>keycloak-core</artifactId>
             <version>${project.version}</version>
diff --git a/integration/adapter-core/src/main/java/org/keycloak/adapters/RefreshableKeycloakSession.java b/integration/adapter-core/src/main/java/org/keycloak/adapters/RefreshableKeycloakSession.java
new file mode 100755
index 0000000..8716e0e
--- /dev/null
+++ b/integration/adapter-core/src/main/java/org/keycloak/adapters/RefreshableKeycloakSession.java
@@ -0,0 +1,84 @@
+package org.keycloak.adapters;
+
+import org.keycloak.KeycloakAuthenticatedSession;
+import org.keycloak.RSATokenVerifier;
+import org.keycloak.VerificationException;
+import org.keycloak.adapters.config.RealmConfiguration;
+import org.keycloak.representations.AccessToken;
+import org.keycloak.representations.AccessTokenResponse;
+import org.jboss.logging.Logger;
+
+import java.io.IOException;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class RefreshableKeycloakSession extends KeycloakAuthenticatedSession {
+
+    protected static Logger log = Logger.getLogger(RefreshableKeycloakSession.class);
+
+    protected transient RealmConfiguration realmConfiguration;
+    protected String refreshToken;
+
+    public RefreshableKeycloakSession() {
+    }
+
+    public RefreshableKeycloakSession(String tokenString, AccessToken token, ResourceMetadata metadata, RealmConfiguration realmConfiguration, String refreshToken) {
+        super(tokenString, token, metadata);
+        this.realmConfiguration = realmConfiguration;
+        this.refreshToken = refreshToken;
+    }
+
+    @Override
+    public AccessToken getToken() {
+        refreshExpiredToken();
+        return super.getToken();
+    }
+
+    @Override
+    public String getTokenString() {
+        refreshExpiredToken();
+        return super.getTokenString();
+    }
+
+    public boolean isActive() {
+        return this.token.isActive();
+    }
+
+    public void setRealmConfiguration(RealmConfiguration realmConfiguration) {
+        this.realmConfiguration = realmConfiguration;
+    }
+
+    public void refreshExpiredToken() {
+        if (this.token.isActive()) return;
+        if (this.realmConfiguration == null || refreshToken == null) return; // Might be serialized in HttpSession?
+
+        log.info("Doing refresh");
+        AccessTokenResponse response = null;
+        try {
+            response = TokenGrantRequest.invokeRefresh(realmConfiguration, refreshToken);
+        } catch (IOException e) {
+            log.error("Refresh token failure", e);
+            return;
+        } catch (TokenGrantRequest.HttpFailure httpFailure) {
+            log.error("Refresh token failure status: " + httpFailure.getStatus() + " " + httpFailure.getError());
+            return;
+        }
+        log.info("received refresh response");
+        String tokenString = response.getToken();
+        AccessToken token = null;
+        try {
+            token = RSATokenVerifier.verifyToken(tokenString, realmConfiguration.getMetadata().getRealmKey(), realmConfiguration.getMetadata().getRealm());
+            log.info("Token Verification succeeded!");
+        } catch (VerificationException e) {
+            log.error("failed verification of token");
+        }
+        this.token = token;
+        this.refreshToken = response.getRefreshToken();
+        this.tokenString = tokenString;
+
+    }
+
+
+}
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 ced0ea2..430b3bd 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
@@ -16,6 +16,7 @@ import org.jboss.logging.Logger;
 import org.keycloak.KeycloakAuthenticatedSession;
 import org.keycloak.KeycloakPrincipal;
 import org.keycloak.adapters.AdapterConstants;
+import org.keycloak.adapters.RefreshableKeycloakSession;
 import org.keycloak.adapters.ResourceMetadata;
 import org.keycloak.adapters.as7.config.CatalinaAdapterConfigLoader;
 import org.keycloak.representations.AccessToken;
@@ -92,6 +93,7 @@ public class KeycloakAuthenticatorValve extends FormAuthenticator implements Lif
                 remoteLogout(input, response);
                 return;
             }
+            checkKeycloakSession(request);
             super.invoke(request, response);
         } finally {
         }
@@ -184,13 +186,39 @@ public class KeycloakAuthenticatorValve extends FormAuthenticator implements Lif
         return false;
     }
 
+    /**
+     * Checks that access token is still valid.  Will attempt refresh of token if it is not.
+     *
+     * @param request
+     */
+    protected void checkKeycloakSession(Request request) {
+        if (request.getSessionInternal(false) == null || request.getSessionInternal().getPrincipal() == null) return;
+        RefreshableKeycloakSession session = (RefreshableKeycloakSession)request.getSessionInternal().getNote(KeycloakAuthenticatedSession.class.getName());
+        if (session == null) return;
+        // just in case session got serialized
+        session.setRealmConfiguration(realmConfiguration);
+        session.setMetadata(resourceMetadata);
+        if (session.isActive()) return;
+
+        // FYI: A refresh requires same scope, so same roles will be set.  Otherwise, refresh will fail and token will
+        // not be updated
+        session.refreshExpiredToken();
+        if (session.isActive()) return;
+
+        request.getSessionInternal().removeNote(KeycloakAuthenticatedSession.class.getName());
+        request.setUserPrincipal(null);
+        request.setAuthType(null);
+        request.getSessionInternal().setPrincipal(null);
+        request.getSessionInternal().setAuthType(null);
+    }
+
     protected boolean checkLoggedIn(Request request, HttpServletResponse response) {
-        if (request.getSessionInternal() == null || request.getSessionInternal().getPrincipal() == null)
+        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("OAUTH");
+        request.setAuthType("KEYCLOAK");
         Session session = request.getSessionInternal();
         if (session != null) {
             KeycloakAuthenticatedSession skSession = (KeycloakAuthenticatedSession) session.getNote(KeycloakAuthenticatedSession.class.getName());
@@ -234,7 +262,7 @@ public class KeycloakAuthenticatorValve extends FormAuthenticator implements Lif
             Session session = request.getSessionInternal(true);
             session.setPrincipal(principal);
             session.setAuthType("OAUTH");
-            KeycloakAuthenticatedSession skSession = new KeycloakAuthenticatedSession(oauth.getTokenString(), token, realmConfiguration.getMetadata());
+            KeycloakAuthenticatedSession skSession = new RefreshableKeycloakSession(oauth.getTokenString(), oauth.getToken(), resourceMetadata, realmConfiguration, oauth.getRefreshToken());
             session.setNote(KeycloakAuthenticatedSession.class.getName(), skSession);
 
             String username = token.getSubject();
diff --git a/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/ServletOAuthLogin.java b/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/ServletOAuthLogin.java
index 2f9ca07..35cb609 100755
--- a/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/ServletOAuthLogin.java
+++ b/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/ServletOAuthLogin.java
@@ -29,6 +29,7 @@ public class ServletOAuthLogin {
     protected int redirectPort;
     protected String tokenString;
     protected AccessToken token;
+    protected String refreshToken;
 
     public ServletOAuthLogin(RealmConfiguration realmInfo, HttpServletRequest request, HttpServletResponse response, int redirectPort) {
         this.request = request;
@@ -45,6 +46,10 @@ public class ServletOAuthLogin {
         return token;
     }
 
+    public String getRefreshToken() {
+        return refreshToken;
+    }
+
     public RealmConfiguration getRealmInfo() {
         return realmInfo;
     }
@@ -249,6 +254,7 @@ public class ServletOAuthLogin {
             sendError(HttpServletResponse.SC_FORBIDDEN);
             return false;
         }
+        refreshToken = tokenResponse.getRefreshToken();
         // redirect to URL without oauth query parameters
         sendRedirect(redirectUri);
         return true;
diff --git a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/KeycloakAuthenticationMechanism.java b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/KeycloakAuthenticationMechanism.java
index 33fd421..5b9ef86 100755
--- a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/KeycloakAuthenticationMechanism.java
+++ b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/KeycloakAuthenticationMechanism.java
@@ -8,6 +8,7 @@ import io.undertow.util.AttachmentKey;
 import org.jboss.logging.Logger;
 import org.keycloak.KeycloakAuthenticatedSession;
 import org.keycloak.KeycloakPrincipal;
+import org.keycloak.adapters.RefreshableKeycloakSession;
 import org.keycloak.adapters.config.RealmConfiguration;
 import org.keycloak.adapters.ResourceMetadata;
 import org.keycloak.representations.AccessToken;
@@ -93,7 +94,8 @@ public class KeycloakAuthenticationMechanism implements AuthenticationMechanism 
 
     protected void completeAuthentication(HttpServerExchange exchange, SecurityContext securityContext, OAuthAuthenticator oauth) {
         final KeycloakPrincipal principal = new KeycloakPrincipal(oauth.getToken().getSubject(), null);
-        KeycloakUndertowAccount account = new KeycloakUndertowAccount(principal, oauth.getToken(), oauth.getTokenString(), oauth.getRefreshToken(), realmConfig, resourceMetadata, adapterConfig);
+        RefreshableKeycloakSession session = new RefreshableKeycloakSession(oauth.getTokenString(), oauth.getToken(), resourceMetadata, realmConfig, oauth.getRefreshToken());
+        KeycloakUndertowAccount account = new KeycloakUndertowAccount(principal, session, adapterConfig, resourceMetadata);
         securityContext.authenticationComplete(account, "KEYCLOAK", true);
         login(exchange, account);
     }
@@ -105,7 +107,8 @@ public class KeycloakAuthenticationMechanism implements AuthenticationMechanism 
 
     protected void completeAuthentication(SecurityContext securityContext, BearerTokenAuthenticator bearer) {
         final KeycloakPrincipal principal = new KeycloakPrincipal(bearer.getToken().getSubject(), bearer.getSurrogate());
-        KeycloakUndertowAccount account = new KeycloakUndertowAccount(principal, bearer.getToken(), bearer.getTokenString(), null, realmConfig, resourceMetadata, adapterConfig);
+        RefreshableKeycloakSession session = new RefreshableKeycloakSession(bearer.getTokenString(), bearer.getToken(), resourceMetadata, realmConfig, null);
+        KeycloakUndertowAccount account = new KeycloakUndertowAccount(principal, session, adapterConfig, resourceMetadata);
         securityContext.authenticationComplete(account, "KEYCLOAK", false);
     }
 
diff --git a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/KeycloakIdentityManager.java b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/KeycloakIdentityManager.java
index 00d4a8f..34406da 100755
--- a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/KeycloakIdentityManager.java
+++ b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/KeycloakIdentityManager.java
@@ -23,29 +23,29 @@ import java.io.IOException;
 */
 class KeycloakIdentityManager implements IdentityManager {
     protected static Logger log = Logger.getLogger(KeycloakIdentityManager.class);
+    protected AdapterConfig adapterConfig;
+    protected RealmConfiguration realmConfiguration;
+
+    KeycloakIdentityManager(AdapterConfig adapterConfig, RealmConfiguration realmConfiguration) {
+        this.adapterConfig = adapterConfig;
+        this.realmConfiguration = realmConfiguration;
+    }
 
     @Override
     public Account verify(Account account) {
         log.info("Verifying account in IdentityManager");
         KeycloakUndertowAccount keycloakAccount = (KeycloakUndertowAccount)account;
-        if (keycloakAccount.getAccessToken().isActive()) {
-            log.info("account is still active.  Time left: " + (keycloakAccount.getAccessToken().getExpiration() - (System.currentTimeMillis()/1000)) );
-            return account;
-        }
-        keycloakAccount.refreshExpiredToken();
-        if (!keycloakAccount.getAccessToken().isActive()) return null;
+        if (!keycloakAccount.isActive(realmConfiguration, adapterConfig)) return null;
         return account;
     }
 
     @Override
     public Account verify(String id, Credential credential) {
-        KeycloakServletExtension.log.warn("Shouldn't call verify!!!");
-        throw new IllegalStateException("Not allowed");
+        throw new IllegalStateException("Unsupported verify method");
     }
 
     @Override
     public Account verify(Credential credential) {
-        KeycloakServletExtension.log.warn("Shouldn't call verify!!!");
-        throw new IllegalStateException("Not allowed");
+        throw new IllegalStateException("Unsupported verify method");
     }
 }
diff --git a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/KeycloakServletExtension.java b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/KeycloakServletExtension.java
index c7948ee..02aff6c 100755
--- a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/KeycloakServletExtension.java
+++ b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/KeycloakServletExtension.java
@@ -95,7 +95,7 @@ public class KeycloakServletExtension implements ServletExtension {
         deploymentInfo.addInnerHandlerChainWrapper(ServletPropagateSessionHandler.WRAPPER); // propagates SkeletonKeySession
         deploymentInfo.addInnerHandlerChainWrapper(actions); // handles authenticated actions and cors.
 
-        deploymentInfo.setIdentityManager(new KeycloakIdentityManager());
+        deploymentInfo.setIdentityManager(new KeycloakIdentityManager(keycloakConfig, realmConfiguration));
 
         log.info("Setting jsession cookie path to: " + deploymentInfo.getContextPath());
         ServletSessionConfig cookieConfig = new ServletSessionConfig();
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 dd2b174..8f02689 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
@@ -3,16 +3,13 @@ package org.keycloak.adapters.undertow;
 import io.undertow.security.idm.Account;
 import org.jboss.logging.Logger;
 import org.keycloak.KeycloakPrincipal;
-import org.keycloak.RSATokenVerifier;
-import org.keycloak.VerificationException;
+import org.keycloak.adapters.RefreshableKeycloakSession;
 import org.keycloak.adapters.ResourceMetadata;
-import org.keycloak.adapters.TokenGrantRequest;
 import org.keycloak.adapters.config.RealmConfiguration;
 import org.keycloak.representations.AccessToken;
-import org.keycloak.representations.AccessTokenResponse;
 import org.keycloak.representations.adapters.config.AdapterConfig;
 
-import java.io.IOException;
+import java.io.Serializable;
 import java.security.Principal;
 import java.util.Collections;
 import java.util.Set;
@@ -21,30 +18,19 @@ import java.util.Set;
 * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
 * @version $Revision: 1 $
 */
-public class KeycloakUndertowAccount implements Account {
+public class KeycloakUndertowAccount implements Account, Serializable {
     protected static Logger log = Logger.getLogger(KeycloakUndertowAccount.class);
-    protected AccessToken accessToken;
-    protected String encodedAccessToken;
-    protected String refreshToken;
+    protected RefreshableKeycloakSession session;
     protected KeycloakPrincipal principal;
     protected Set<String> accountRoles;
-    protected RealmConfiguration realmConfiguration;
-    protected ResourceMetadata resourceMetadata;
-    protected AdapterConfig adapterConfig;
 
-    public KeycloakUndertowAccount(KeycloakPrincipal principal, AccessToken accessToken, String encodedAccessToken, String refreshToken,
-                                   RealmConfiguration realmConfiguration, ResourceMetadata resourceMetadata, AdapterConfig adapterConfig) {
+    public KeycloakUndertowAccount(KeycloakPrincipal principal, RefreshableKeycloakSession session, AdapterConfig config, ResourceMetadata metadata) {
         this.principal = principal;
-        this.accessToken = accessToken;
-        this.encodedAccessToken = encodedAccessToken;
-        this.refreshToken = refreshToken;
-        this.realmConfiguration = realmConfiguration;
-        this.resourceMetadata = resourceMetadata;
-        this.adapterConfig = adapterConfig;
-        setRoles(accessToken);
+        this.session = session;
+        setRoles(session.getToken(), config, metadata);
     }
 
-    protected void setRoles(AccessToken accessToken) {
+    protected void setRoles(AccessToken accessToken, AdapterConfig adapterConfig, ResourceMetadata resourceMetadata) {
         Set<String> roles = null;
         if (adapterConfig.isUseResourceRoleMappings()) {
             AccessToken.Access access = accessToken.getResourceAccess(resourceMetadata.getResourceName());
@@ -68,48 +54,30 @@ public class KeycloakUndertowAccount implements Account {
     }
 
     public AccessToken getAccessToken() {
-        return accessToken;
+        return session.getToken();
     }
 
     public String getEncodedAccessToken() {
-        return encodedAccessToken;
+        return session.getTokenString();
     }
 
-    public String getRefreshToken() {
-        return refreshToken;
+    public RefreshableKeycloakSession getSession() {
+        return session;
     }
 
-    public ResourceMetadata getResourceMetadata() {
-        return resourceMetadata;
-    }
+    public boolean isActive(RealmConfiguration realmConfiguration, AdapterConfig config) {
+        // this object may have been serialized, so we need to reset realm config/metadata
+        session.setRealmConfiguration(realmConfiguration);
+        session.setMetadata(realmConfiguration.getMetadata());
+        if (session.isActive()) return true;
 
-    public void refreshExpiredToken() {
-        if (accessToken.isActive()) return;
-
-        log.info("Doing refresh");
-        AccessTokenResponse response = null;
-        try {
-            response = TokenGrantRequest.invokeRefresh(realmConfiguration, getRefreshToken());
-        } catch (IOException e) {
-            log.error("Refresh token failure", e);
-            return;
-        } catch (TokenGrantRequest.HttpFailure httpFailure) {
-            log.error("Refresh token failure status: " + httpFailure.getStatus() + " " + httpFailure.getError());
-            return;
-        }
-        log.info("received refresh response");
-        String tokenString = response.getToken();
-        AccessToken token = null;
-        try {
-            token = RSATokenVerifier.verifyToken(tokenString, realmConfiguration.getMetadata().getRealmKey(), realmConfiguration.getMetadata().getRealm());
-            log.info("Token Verification succeeded!");
-        } catch (VerificationException e) {
-            log.error("failed verification of token");
-        }
-        this.accessToken = token;
-        this.refreshToken = response.getRefreshToken();
-        this.encodedAccessToken = tokenString;
-        setRoles(this.accessToken);
+        session.refreshExpiredToken();
+        if (!session.isActive()) return false;
 
+        setRoles(session.getToken(), config, realmConfiguration.getMetadata());
+        return true;
     }
+
+
+
 }
diff --git a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletPropagateSessionHandler.java b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletPropagateSessionHandler.java
index 9baa829..efd5662 100755
--- a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletPropagateSessionHandler.java
+++ b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletPropagateSessionHandler.java
@@ -40,12 +40,10 @@ public class ServletPropagateSessionHandler implements HttpHandler {
             next.handleRequest(exchange);
             return;
         }
-        UndertowKeycloakSession skSession = new UndertowKeycloakSession(account);
-
 
         final ServletRequestContext servletRequestContext = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY);
         HttpServletRequest req = (HttpServletRequest) servletRequestContext.getServletRequest();
-        req.setAttribute(KeycloakAuthenticatedSession.class.getName(), skSession);
+        req.setAttribute(KeycloakAuthenticatedSession.class.getName(), account.getSession());
 
         HttpSession session = req.getSession(false);
         if (session == null) {
@@ -53,7 +51,7 @@ public class ServletPropagateSessionHandler implements HttpHandler {
             return;
         }
         log.debug("propagating to HTTP Session");
-        session.setAttribute(KeycloakAuthenticatedSession.class.getName(), skSession);
+        session.setAttribute(KeycloakAuthenticatedSession.class.getName(), account.getSession());
         next.handleRequest(exchange);
     }
 }
diff --git a/services/src/main/java/org/keycloak/services/managers/AccessCodeEntry.java b/services/src/main/java/org/keycloak/services/managers/AccessCodeEntry.java
index e3268cf..a94cb1b 100755
--- a/services/src/main/java/org/keycloak/services/managers/AccessCodeEntry.java
+++ b/services/src/main/java/org/keycloak/services/managers/AccessCodeEntry.java
@@ -22,6 +22,7 @@ public class AccessCodeEntry {
     protected String code;
     protected String state;
     protected String redirectUri;
+    protected boolean rememberMe;
 
     protected long expiration;
     protected RealmModel realm;
@@ -119,4 +120,12 @@ public class AccessCodeEntry {
     public void setRedirectUri(String redirectUri) {
         this.redirectUri = redirectUri;
     }
+
+    public boolean isRememberMe() {
+        return rememberMe;
+    }
+
+    public void setRememberMe(boolean rememberMe) {
+        this.rememberMe = rememberMe;
+    }
 }
diff --git a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
index 237b7dc..b82f585 100755
--- a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
@@ -39,6 +39,7 @@ public class AuthenticationManager {
     protected static Logger logger = Logger.getLogger(AuthenticationManager.class);
     public static final String FORM_USERNAME = "username";
     public static final String KEYCLOAK_IDENTITY_COOKIE = "KEYCLOAK_IDENTITY";
+    public static final String KEYCLOAK_REMEMBER_ME = "KEYCLOAK_REMEMBER_ME";
 
     public AccessToken createIdentityToken(RealmModel realm, UserModel user) {
         AccessToken token = new AccessToken();
@@ -52,26 +53,26 @@ public class AuthenticationManager {
         return token;
     }
 
-    public NewCookie createLoginCookie(RealmModel realm, UserModel user, UriInfo uriInfo) {
+    public NewCookie createLoginCookie(RealmModel realm, UserModel user, UriInfo uriInfo, boolean rememberMe) {
         String cookieName = KEYCLOAK_IDENTITY_COOKIE;
         String cookiePath = getIdentityCookiePath(realm, uriInfo);
-        return createLoginCookie(realm, user, null, cookieName, cookiePath);
+        return createLoginCookie(realm, user, null, cookieName, cookiePath, rememberMe);
     }
 
     public NewCookie createSaasIdentityCookie(RealmModel realm, UserModel user, UriInfo uriInfo) {
         String cookieName = AdminService.SAAS_IDENTITY_COOKIE;
         URI uri = AdminService.saasCookiePath(uriInfo).build();
         String cookiePath = uri.getRawPath();
-        return createLoginCookie(realm, user, null, cookieName, cookiePath);
+        return createLoginCookie(realm, user, null, cookieName, cookiePath, false);
     }
 
     public NewCookie createAccountIdentityCookie(RealmModel realm, UserModel user, UserModel client, URI uri) {
         String cookieName = AccountService.ACCOUNT_IDENTITY_COOKIE;
         String cookiePath = uri.getRawPath();
-        return createLoginCookie(realm, user, client, cookieName, cookiePath);
+        return createLoginCookie(realm, user, client, cookieName, cookiePath, false);
     }
 
-    protected NewCookie createLoginCookie(RealmModel realm, UserModel user, UserModel client, String cookieName, String cookiePath) {
+    protected NewCookie createLoginCookie(RealmModel realm, UserModel user, UserModel client, String cookieName, String cookiePath, boolean rememberMe) {
         AccessToken identityToken = createIdentityToken(realm, user);
         if (client != null) {
             identityToken.issuedFor(client.getLoginName());
@@ -80,15 +81,22 @@ public class AuthenticationManager {
         boolean secureOnly = !realm.isSslNotRequired();
         logger.debug("creatingLoginCookie - name: {0} path: {1}", cookieName, cookiePath);
         int maxAge = NewCookie.DEFAULT_MAX_AGE;
-        /*
-        if (realm.isRememberMe()) {
+        if (rememberMe) {
             maxAge = realm.getCentralLoginLifespan();
+            logger.info("createLoginCookie maxAge: " + maxAge);
         }
-        */
         NewCookie cookie = new NewCookie(cookieName, encoded, cookiePath, null, null, maxAge, secureOnly, true);
         return cookie;
     }
 
+    public NewCookie createRememberMeCookie(RealmModel realm, UriInfo uriInfo) {
+        String path = getIdentityCookiePath(realm, uriInfo);
+        boolean secureOnly = !realm.isSslNotRequired();
+        // remember me cookie should be persistent
+        NewCookie cookie = new NewCookie(KEYCLOAK_REMEMBER_ME, "true", path, null, null, realm.getCentralLoginLifespan(), secureOnly, true);
+        return cookie;
+    }
+
     protected String encodeToken(RealmModel realm, Object token) {
         String encodedToken = new JWSBuilder()
                 .jsonContent(token)
@@ -103,6 +111,12 @@ public class AuthenticationManager {
         String cookieName = KEYCLOAK_IDENTITY_COOKIE;
         expireCookie(cookieName, path);
     }
+    public void expireRememberMeCookie(RealmModel realm, UriInfo uriInfo) {
+        logger.debug("Expiring remember me cookie");
+        String path = getIdentityCookiePath(realm, uriInfo);
+        String cookieName = KEYCLOAK_REMEMBER_ME;
+        expireCookie(cookieName, path);
+    }
 
     protected String getIdentityCookiePath(RealmModel realm, UriInfo uriInfo) {
         URI uri = RealmsResource.realmBaseUrl(uriInfo).build(realm.getName());
diff --git a/services/src/main/java/org/keycloak/services/resources/flows/OAuthFlows.java b/services/src/main/java/org/keycloak/services/resources/flows/OAuthFlows.java
index 626aab8..8524807 100755
--- a/services/src/main/java/org/keycloak/services/resources/flows/OAuthFlows.java
+++ b/services/src/main/java/org/keycloak/services/resources/flows/OAuthFlows.java
@@ -35,6 +35,7 @@ import org.keycloak.services.managers.AuthenticationManager;
 import org.keycloak.services.managers.TokenManager;
 import org.keycloak.services.resources.TokenService;
 
+import javax.ws.rs.core.Cookie;
 import javax.ws.rs.core.Response;
 import javax.ws.rs.core.UriBuilder;
 import javax.ws.rs.core.UriInfo;
@@ -69,13 +70,20 @@ public class OAuthFlows {
     }
 
     public Response redirectAccessCode(AccessCodeEntry accessCode, String state, String redirect) {
+        return redirectAccessCode(accessCode, state, redirect, false);
+    }
+
+
+    public Response redirectAccessCode(AccessCodeEntry accessCode, String state, String redirect, boolean rememberMe) {
         String code = accessCode.getCode();
         UriBuilder redirectUri = UriBuilder.fromUri(redirect).queryParam("code", code);
         log.debug("redirectAccessCode: state: {0}", state);
         if (state != null)
             redirectUri.queryParam("state", state);
         Response.ResponseBuilder location = Response.status(302).location(redirectUri.build());
-        location.cookie(authManager.createLoginCookie(realm, accessCode.getUser(), uriInfo));
+        Cookie remember = request.getHttpHeaders().getCookies().get(AuthenticationManager.KEYCLOAK_REMEMBER_ME);
+        rememberMe = rememberMe || remember != null;
+        location.cookie(authManager.createLoginCookie(realm, accessCode.getUser(), uriInfo, rememberMe));
         return location.build();
     }
 
@@ -89,6 +97,11 @@ public class OAuthFlows {
     }
 
     public Response processAccessCode(String scopeParam, String state, String redirect, UserModel client, UserModel user) {
+        return processAccessCode(scopeParam, state, redirect, client, user, false);
+    }
+
+
+    public Response processAccessCode(String scopeParam, String state, String redirect, UserModel client, UserModel user, boolean rememberMe) {
         isTotpConfigurationRequired(user);
         isEmailVerificationRequired(user);
 
@@ -121,7 +134,7 @@ public class OAuthFlows {
         }
 
         if (redirect != null) {
-            return redirectAccessCode(accessCode, state, redirect);
+            return redirectAccessCode(accessCode, state, redirect, rememberMe);
         } else {
             return null;
         }
diff --git a/services/src/main/java/org/keycloak/services/resources/TokenService.java b/services/src/main/java/org/keycloak/services/resources/TokenService.java
index 4983d37..24d8090 100755
--- a/services/src/main/java/org/keycloak/services/resources/TokenService.java
+++ b/services/src/main/java/org/keycloak/services/resources/TokenService.java
@@ -45,6 +45,7 @@ import javax.ws.rs.core.Context;
 import javax.ws.rs.core.HttpHeaders;
 import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.core.NewCookie;
 import javax.ws.rs.core.Response;
 import javax.ws.rs.core.SecurityContext;
 import javax.ws.rs.core.UriBuilder;
@@ -235,10 +236,20 @@ public class TokenService {
 
         AuthenticationStatus status = authManager.authenticateForm(realm, user, formData);
 
+        String rememberMe = formData.getFirst("rememberMe");
+        boolean remember = rememberMe != null && rememberMe.equalsIgnoreCase("on");
+        logger.debug("*** Remember me: " + remember);
+        if (remember) {
+            NewCookie cookie = authManager.createRememberMeCookie(realm, uriInfo);
+            response.addNewCookie(cookie);
+        } else {
+            authManager.expireRememberMeCookie(realm, uriInfo);
+        }
+
         switch (status) {
             case SUCCESS:
             case ACTIONS_REQUIRED:
-                return oauth.processAccessCode(scopeParam, state, redirect, client, user);
+                return oauth.processAccessCode(scopeParam, state, redirect, client, user, remember);
             case ACCOUNT_DISABLED:
                 return Flows.forms(realm, request, uriInfo).setError(Messages.ACCOUNT_DISABLED).setFormData(formData).createLogin();
             case MISSING_TOTP:
@@ -544,6 +555,7 @@ public class TokenService {
         if (user != null) {
             logger.info("Logging out: {0}", user.getLoginName());
             authManager.expireIdentityCookie(realm, uriInfo);
+            authManager.expireRememberMeCookie(realm, uriInfo);
             resourceAdminManager.singleLogOut(realm, user.getId());
         } else {
             logger.info("No user logged in for logout");