keycloak-aplcache

Changes

integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletAuthenticatedActionsHandler.java 48(+0 -48)

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 3109d6d..44b024b 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
@@ -543,10 +543,16 @@ module.controller('RealmTokenDetailCtrl', function($scope, Realm, realm, $http, 
 
     $scope.realm = realm;
 
-    $scope.realm.tokenLifespanUnit = TimeUnit.autoUnit(realm.tokenLifespan);
-    $scope.realm.tokenLifespan = TimeUnit.toUnit(realm.tokenLifespan, $scope.realm.tokenLifespanUnit);
-    $scope.$watch('realm.tokenLifespanUnit', function(to, from) {
-        $scope.realm.tokenLifespan = TimeUnit.convert($scope.realm.tokenLifespan, from, to);
+    $scope.realm.accessTokenLifespanUnit = TimeUnit.autoUnit(realm.accessTokenLifespan);
+    $scope.realm.accessTokenLifespan = TimeUnit.toUnit(realm.accessTokenLifespan, $scope.realm.accessTokenLifespanUnit);
+    $scope.$watch('realm.accessTokenLifespanUnit', function(to, from) {
+        $scope.realm.accessTokenLifespan = TimeUnit.convert($scope.realm.accessTokenLifespan, from, to);
+    });
+
+    $scope.realm.refreshTokenLifespanUnit = TimeUnit.autoUnit(realm.refreshTokenLifespan);
+    $scope.realm.refreshTokenLifespan = TimeUnit.toUnit(realm.refreshTokenLifespan, $scope.realm.refreshTokenLifespanUnit);
+    $scope.$watch('realm.refreshTokenLifespanUnit', function(to, from) {
+        $scope.realm.refreshTokenLifespan = TimeUnit.convert($scope.realm.refreshTokenLifespan, from, to);
     });
 
     $scope.realm.accessCodeLifespanUnit = TimeUnit.autoUnit(realm.accessCodeLifespan);
@@ -573,11 +579,13 @@ module.controller('RealmTokenDetailCtrl', function($scope, Realm, realm, $http, 
 
     $scope.save = function() {
         var realmCopy = angular.copy($scope.realm);
-        delete realmCopy["tokenLifespanUnit"];
+        delete realmCopy["accessTokenLifespanUnit"];
+        delete realmCopy["refreshTokenLifespanUnit"];
         delete realmCopy["accessCodeLifespanUnit"];
         delete realmCopy["accessCodeLifespanUserActionUnit"];
 
-        realmCopy.tokenLifespan = TimeUnit.toSeconds($scope.realm.tokenLifespan, $scope.realm.tokenLifespanUnit)
+        realmCopy.accessTokenLifespan = TimeUnit.toSeconds($scope.realm.accessTokenLifespan, $scope.realm.accessTokenLifespanUnit)
+        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/admin-ui/src/main/resources/META-INF/resources/admin/partials/realm-tokens.html b/admin-ui/src/main/resources/META-INF/resources/admin/partials/realm-tokens.html
index 74b9b36..4e8ada4 100755
--- a/admin-ui/src/main/resources/META-INF/resources/admin/partials/realm-tokens.html
+++ b/admin-ui/src/main/resources/META-INF/resources/admin/partials/realm-tokens.html
@@ -11,7 +11,7 @@
         <form class="form-horizontal" name="realmForm" novalidate>
             <fieldset class="border-top">
                 <div class="form-group input-select">
-                    <label class="col-sm-2 control-label" for="accessTokenLifespan">Access Token lifespan</label>
+                    <label class="col-sm-2 control-label" for="accessTokenLifespan">Access token lifespan</label>
                     <div class="col-sm-10">
                         <div class="row">
                             <div class="col-sm-2">
@@ -20,8 +20,8 @@
                                        id="accessTokenLifespan" name="accessTokenLifespan"/>
                             </div>
                             <div class="col-sm-2 select-kc">
-                                <select name="tokenLifespanUnit" data-ng-model="realm.tokenLifespanUnit" >
-                                    <option data-ng-selected="!realm.tokenLifespanUnit">Seconds</option>
+                                <select name="accessTokenLifespanUnit" data-ng-model="realm.accessTokenLifespanUnit" >
+                                    <option data-ng-selected="!realm.accessTokenLifespanUnit">Seconds</option>
                                     <option>Minutes</option>
                                     <option>Hours</option>
                                     <option>Days</option>
@@ -66,6 +66,26 @@
                         </div>
                     </div>
                 </div>
+                <div class="form-group input-select">
+                    <label class="col-sm-2 control-label" for="refreshTokenLifespan">Refresh token lifespan {{realm.refreshTokenLifespan}}</label>
+                    <div class="col-sm-10">
+                        <div class="row">
+                            <div class="col-sm-2">
+                                <input class="form-control" type="number" required min="1"
+                                       max="31536000" data-ng-model="realm.refreshTokenLifespan"
+                                       id="refreshTokenLifespan" name="refreshTokenLifespan"/>
+                            </div>
+                            <div class="col-sm-2 select-kc">
+                                <select name="refreshTokenLifespanUnit" data-ng-model="realm.refreshTokenLifespanUnit" >
+                                    <option data-ng-selected="!realm.refreshTokenLifespanUnit">Seconds</option>
+                                    <option>Minutes</option>
+                                    <option>Hours</option>
+                                    <option>Days</option>
+                                </select>
+                            </div>
+                        </div>
+                    </div>
+                </div>
             </fieldset>
             <div class="pull-right form-actions">
                 <button kc-reset data-ng-show="changed">Clear changes</button>
diff --git a/core/src/main/java/org/keycloak/OAuthErrorException.java b/core/src/main/java/org/keycloak/OAuthErrorException.java
new file mode 100755
index 0000000..3cddba2
--- /dev/null
+++ b/core/src/main/java/org/keycloak/OAuthErrorException.java
@@ -0,0 +1,62 @@
+package org.keycloak;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class OAuthErrorException extends Exception {
+    public static final String INVALID_REQUEST = "invalid_request";
+    public static final String INVALID_CLIENT = "invalid_client";
+    public static final String INVALID_GRANT = "invalid_grant";
+    public static final String INVALID_SCOPE = "invalid_grant";
+    public static final String UNAUTHORIZED_CLIENT = "unauthorized_client";
+    public static final String UNSUPPORTED_GRANT_TYPE = "unsupported_grant_type";
+
+    public OAuthErrorException(String error, String description, String message, Throwable cause) {
+        super(message, cause);
+        this.error = error;
+        this.description = description;
+    }
+    public OAuthErrorException(String error, String description, String message) {
+        super(message);
+    }
+    public OAuthErrorException(String error, String description) {
+        super(description);
+        this.error = error;
+        this.description = description;
+    }
+    public OAuthErrorException(String error, String description, Throwable cause) {
+        super(description, cause);
+        this.error = error;
+        this.description = description;
+    }
+
+    public OAuthErrorException(String error) {
+        super(error);
+        this.error = error;
+    }
+    public OAuthErrorException(String error, Throwable cause) {
+        super(error, cause);
+        this.error = error;
+    }
+
+
+    protected String error;
+    protected String description;
+
+    public String getError() {
+        return error;
+    }
+
+    public void setError(String error) {
+        this.error = error;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+    public void setDescription(String description) {
+        this.description = description;
+    }
+}
diff --git a/core/src/main/java/org/keycloak/representations/AccessToken.java b/core/src/main/java/org/keycloak/representations/AccessToken.java
index 8341c57..c99a509 100755
--- a/core/src/main/java/org/keycloak/representations/AccessToken.java
+++ b/core/src/main/java/org/keycloak/representations/AccessToken.java
@@ -79,6 +79,13 @@ public class AccessToken extends JsonWebToken {
         return resourceAccess;
     }
 
+    public void setResourceAccess(Map<String, Access> resourceAccess) {
+        this.resourceAccess = resourceAccess;
+    }
+
+
+
+
     /**
      * Does the realm require verifying the caller?
      *
@@ -130,6 +137,7 @@ public class AccessToken extends JsonWebToken {
         return (AccessToken) super.notBefore(notBefore);
     }
 
+
     @Override
     public AccessToken issuedAt(long issuedAt) {
         return (AccessToken) super.issuedAt(issuedAt);
diff --git a/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java
index e4d70bb..acdca83 100755
--- a/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java
+++ b/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java
@@ -14,6 +14,7 @@ public class RealmRepresentation {
     protected String id;
     protected String realm;
     protected Integer accessTokenLifespan;
+    protected Integer refreshTokenLifespan;
     protected Integer accessCodeLifespan;
     protected Integer accessCodeLifespanUserAction;
     protected Boolean enabled;
@@ -122,6 +123,14 @@ public class RealmRepresentation {
         this.accessTokenLifespan = accessTokenLifespan;
     }
 
+    public Integer getRefreshTokenLifespan() {
+        return refreshTokenLifespan;
+    }
+
+    public void setRefreshTokenLifespan(Integer refreshTokenLifespan) {
+        this.refreshTokenLifespan = refreshTokenLifespan;
+    }
+
     public List<UserRoleMappingRepresentation> getRoleMappings() {
         return roleMappings;
     }
diff --git a/core/src/main/java/org/keycloak/RSATokenVerifier.java b/core/src/main/java/org/keycloak/RSATokenVerifier.java
index 5e5d25b..9530442 100755
--- a/core/src/main/java/org/keycloak/RSATokenVerifier.java
+++ b/core/src/main/java/org/keycloak/RSATokenVerifier.java
@@ -12,8 +12,12 @@ import java.security.PublicKey;
  * @version $Revision: 1 $
  */
 public class RSATokenVerifier {
-
     public static AccessToken verifyToken(String tokenString, PublicKey realmKey, String realm) throws VerificationException {
+        return verifyToken(tokenString, realmKey, realm, true);
+    }
+
+
+    public static AccessToken verifyToken(String tokenString, PublicKey realmKey, String realm, boolean checkActive) throws VerificationException {
         JWSInput input = new JWSInput(tokenString);
         boolean verified = false;
         try {
@@ -29,9 +33,6 @@ public class RSATokenVerifier {
         } catch (IOException e) {
             throw new VerificationException(e);
         }
-        if (!token.isActive()) {
-            throw new VerificationException("Token is not active.");
-        }
         String user = token.getSubject();
         if (user == null) {
             throw new VerificationException("Token user was null");
@@ -40,6 +41,10 @@ public class RSATokenVerifier {
             throw new VerificationException("Token audience doesn't match domain");
 
         }
+        if (checkActive && !token.isActive()) {
+            throw new VerificationException("Token is not active.");
+        }
+
         return token;
     }
 }
diff --git a/core/src/main/java/org/keycloak/ServiceUrlConstants.java b/core/src/main/java/org/keycloak/ServiceUrlConstants.java
index b1c9aa4..f40d83e 100755
--- a/core/src/main/java/org/keycloak/ServiceUrlConstants.java
+++ b/core/src/main/java/org/keycloak/ServiceUrlConstants.java
@@ -8,4 +8,5 @@ public interface ServiceUrlConstants {
 
     public static final String TOKEN_SERVICE_LOGIN_PATH = "/rest/realms/{realm-name}/tokens/login";
     public static final String TOKEN_SERVICE_ACCESS_CODE_PATH = "/rest/realms/{realm-name}/tokens/access/codes";
+    public static final String TOKEN_SERVICE_REFRESH_PATH = "/rest/realms/{realm-name}/tokens/refresh";
 }
diff --git a/integration/adapter-core/src/main/java/org/keycloak/adapters/config/RealmConfiguration.java b/integration/adapter-core/src/main/java/org/keycloak/adapters/config/RealmConfiguration.java
index fe4cf3a..18537f2 100755
--- a/integration/adapter-core/src/main/java/org/keycloak/adapters/config/RealmConfiguration.java
+++ b/integration/adapter-core/src/main/java/org/keycloak/adapters/config/RealmConfiguration.java
@@ -16,6 +16,7 @@ public class RealmConfiguration {
     protected HttpClient client;
     protected KeycloakUriBuilder authUrl;
     protected String codeUrl;
+    protected String refreshUrl;
     protected Map<String, String> resourceCredentials = new HashMap<String, String>();
     protected boolean sslRequired = true;
     protected String stateCookieName = "OAuth_Token_Request_State";
@@ -72,6 +73,14 @@ public class RealmConfiguration {
         this.codeUrl = codeUrl;
     }
 
+    public String getRefreshUrl() {
+        return refreshUrl;
+    }
+
+    public void setRefreshUrl(String refreshUrl) {
+        this.refreshUrl = refreshUrl;
+    }
+
     public Map<String, String> getResourceCredentials() {
         return resourceCredentials;
     }
diff --git a/integration/adapter-core/src/main/java/org/keycloak/adapters/config/RealmConfigurationLoader.java b/integration/adapter-core/src/main/java/org/keycloak/adapters/config/RealmConfigurationLoader.java
index 264fb6b..a1b1e2c 100755
--- a/integration/adapter-core/src/main/java/org/keycloak/adapters/config/RealmConfigurationLoader.java
+++ b/integration/adapter-core/src/main/java/org/keycloak/adapters/config/RealmConfigurationLoader.java
@@ -37,6 +37,7 @@ public class RealmConfigurationLoader extends AdapterConfigLoader {
         KeycloakUriBuilder serverBuilder = KeycloakUriBuilder.fromUri(adapterConfig.getAuthServerUrl());
         String authUrl = serverBuilder.clone().path(ServiceUrlConstants.TOKEN_SERVICE_LOGIN_PATH).build(adapterConfig.getRealm()).toString();
         String tokenUrl = serverBuilder.clone().path(ServiceUrlConstants.TOKEN_SERVICE_ACCESS_CODE_PATH).build(adapterConfig.getRealm()).toString();
+        String refreshUrl = serverBuilder.clone().path(ServiceUrlConstants.TOKEN_SERVICE_REFRESH_PATH).build(adapterConfig.getRealm()).toString();
 
         realmConfiguration.setMetadata(resourceMetadata);
         realmConfiguration.setSslRequired(!adapterConfig.isSslNotRequired());
@@ -47,6 +48,7 @@ public class RealmConfigurationLoader extends AdapterConfigLoader {
         realmConfiguration.setClient(client);
         realmConfiguration.setAuthUrl(KeycloakUriBuilder.fromUri(authUrl).queryParam("client_id", resourceMetadata.getResourceName()));
         realmConfiguration.setCodeUrl(tokenUrl);
+        realmConfiguration.setRefreshUrl(refreshUrl);
     }
 
     protected void initClient() {
diff --git a/integration/adapter-core/src/main/java/org/keycloak/adapters/TokenGrantRequest.java b/integration/adapter-core/src/main/java/org/keycloak/adapters/TokenGrantRequest.java
index 4f3d2ff..029356d 100755
--- a/integration/adapter-core/src/main/java/org/keycloak/adapters/TokenGrantRequest.java
+++ b/integration/adapter-core/src/main/java/org/keycloak/adapters/TokenGrantRequest.java
@@ -46,21 +46,18 @@ public class TokenGrantRequest {
         }
     }
 
-    public static AccessTokenResponse invoke(RealmConfiguration config, String code, String redirectUri) throws HttpFailure, IOException {
+    public static AccessTokenResponse invokeAccessCodeToToken(RealmConfiguration config, String code, String redirectUri) throws HttpFailure, IOException {
         String codeUrl = config.getCodeUrl();
         String client_id = config.getMetadata().getResourceName();
         Map<String,String> credentials = config.getResourceCredentials();
         HttpClient client = config.getClient();
 
-        return invoke(client, code, codeUrl, redirectUri, client_id, credentials);
+        return invokeAccessCodeToToken(client, code, codeUrl, redirectUri, client_id, credentials);
     }
 
-    public static AccessTokenResponse invoke(HttpClient client, String code, String codeUrl, String redirectUri, String client_id, Map<String, String> credentials) throws IOException, HttpFailure {
+    public static AccessTokenResponse invokeAccessCodeToToken(HttpClient client, String code, String codeUrl, String redirectUri, String client_id, Map<String, String> credentials) throws IOException, HttpFailure {
         List<NameValuePair> formparams = new ArrayList<NameValuePair>();
         redirectUri = stripOauthParametersFromRedirect(redirectUri);
-        for (Map.Entry<String, String> entry : credentials.entrySet()) {
-            formparams.add(new BasicNameValuePair(entry.getKey(), entry.getValue()));
-        }
         formparams.add(new BasicNameValuePair("grant_type", "authorization_code"));
         formparams.add(new BasicNameValuePair("code", code));
         formparams.add(new BasicNameValuePair("redirect_uri", redirectUri));
@@ -106,6 +103,64 @@ public class TokenGrantRequest {
         }
     }
 
+    public static AccessTokenResponse invokeRefresh(RealmConfiguration config, String refreshToken) throws IOException, HttpFailure {
+        String refreshUrl = config.getRefreshUrl();
+        String client_id = config.getMetadata().getResourceName();
+        Map<String,String> credentials = config.getResourceCredentials();
+        HttpClient client = config.getClient();
+        return invokeRefresh(client, refreshToken, refreshUrl, client_id, credentials);
+    }
+
+
+    public static AccessTokenResponse invokeRefresh(HttpClient client, String refreshToken, String refreshUrl, String client_id, Map<String, String> credentials) throws IOException, HttpFailure {
+        List<NameValuePair> formparams = new ArrayList<NameValuePair>();
+        for (Map.Entry<String, String> entry : credentials.entrySet()) {
+            formparams.add(new BasicNameValuePair(entry.getKey(), entry.getValue()));
+        }
+        formparams.add(new BasicNameValuePair("grant_type", "refresh_token"));
+        formparams.add(new BasicNameValuePair("refresh_token", refreshToken));
+        HttpResponse response = null;
+        UrlEncodedFormEntity form = new UrlEncodedFormEntity(formparams, "UTF-8");
+        HttpPost post = new HttpPost(refreshUrl);
+        String clientSecret = credentials.get(CredentialRepresentation.SECRET);
+        if (clientSecret != null) {
+            String authorization = BasicAuthHelper.createHeader(client_id, clientSecret);
+            post.setHeader("Authorization", authorization);
+        }
+
+        post.setEntity(form);
+        response = client.execute(post);
+        int status = response.getStatusLine().getStatusCode();
+        HttpEntity entity = response.getEntity();
+        if (status != 200) {
+            error(status, entity);
+        }
+        if (entity == null) {
+            throw new HttpFailure(status, null);
+        }
+        InputStream is = entity.getContent();
+        try {
+            ByteArrayOutputStream os = new ByteArrayOutputStream();
+            int c;
+            while ((c = is.read()) != -1) {
+                os.write(c);
+            }
+            byte[] bytes = os.toByteArray();
+            String json = new String(bytes);
+            try {
+                return JsonSerialization.readValue(json, AccessTokenResponse.class);
+            } catch (IOException e) {
+                throw new IOException(json, e);
+            }
+        } finally {
+            try {
+                is.close();
+            } catch (IOException ignored) {
+
+            }
+        }
+    }
+
 
     protected static void error(int status, HttpEntity entity) throws HttpFailure, IOException {
        String body = null;
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 23fb2bd..ced0ea2 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
@@ -58,6 +58,7 @@ public class KeycloakAuthenticatorValve extends FormAuthenticator implements Lif
         super.start();
         StandardContext standardContext = (StandardContext) context;
         standardContext.addLifecycleListener(this);
+        cache = false;
     }
 
     @Override
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 a2b3c30..2f9ca07 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
@@ -225,7 +225,7 @@ public class ServletOAuthLogin {
         String redirectUri = stripOauthParametersFromRedirect();
         AccessTokenResponse tokenResponse = null;
         try {
-            tokenResponse = TokenGrantRequest.invoke(realmInfo, code, redirectUri);
+            tokenResponse = TokenGrantRequest.invokeAccessCodeToToken(realmInfo, code, redirectUri);
         } catch (TokenGrantRequest.HttpFailure failure) {
             log.error("failed to turn code into token");
             log.error("status from server: " + failure.getStatus());
diff --git a/integration/servlet-oauth-client/src/main/java/org/keycloak/servlet/ServletOAuthClient.java b/integration/servlet-oauth-client/src/main/java/org/keycloak/servlet/ServletOAuthClient.java
index 4290651..5d17c0a 100755
--- a/integration/servlet-oauth-client/src/main/java/org/keycloak/servlet/ServletOAuthClient.java
+++ b/integration/servlet-oauth-client/src/main/java/org/keycloak/servlet/ServletOAuthClient.java
@@ -11,8 +11,6 @@ import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 import java.io.IOException;
 import java.net.URI;
-import java.util.HashMap;
-import java.util.Map;
 
 /**
  * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@@ -48,7 +46,7 @@ public class ServletOAuthClient extends AbstractOAuthClient {
     }
 
     public String resolveBearerToken(String redirectUri, String code) throws IOException, TokenGrantRequest.HttpFailure {
-        return TokenGrantRequest.invoke(client, code, codeUrl, redirectUri, clientId, credentials).getToken();
+        return TokenGrantRequest.invokeAccessCodeToToken(client, code, codeUrl, redirectUri, clientId, credentials).getToken();
     }
 
     /**
diff --git a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/AuthenticatedActionsHandler.java b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/AuthenticatedActionsHandler.java
index 2373283..068f982 100755
--- a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/AuthenticatedActionsHandler.java
+++ b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/AuthenticatedActionsHandler.java
@@ -1,11 +1,11 @@
 package org.keycloak.adapters.undertow;
 
+import io.undertow.server.HandlerWrapper;
 import io.undertow.server.HttpHandler;
 import io.undertow.server.HttpServerExchange;
 import io.undertow.util.Headers;
 import io.undertow.util.StatusCodes;
 import org.jboss.logging.Logger;
-import org.keycloak.KeycloakAuthenticatedSession;
 import org.keycloak.adapters.AdapterConstants;
 import org.keycloak.representations.AccessToken;
 import org.keycloak.representations.adapters.config.AdapterConfig;
@@ -30,6 +30,20 @@ public class AuthenticatedActionsHandler implements HttpHandler {
     protected AdapterConfig adapterConfig;
     protected HttpHandler next;
 
+    public static class Wrapper implements HandlerWrapper {
+        protected AdapterConfig config;
+
+        public Wrapper(AdapterConfig config) {
+            this.config = config;
+        }
+
+        @Override
+        public HttpHandler wrap(HttpHandler handler) {
+            return new AuthenticatedActionsHandler(config, handler);
+        }
+    }
+
+
     protected AuthenticatedActionsHandler(AdapterConfig config, HttpHandler next) {
         this.adapterConfig = config;
         this.next = next;
@@ -38,34 +52,32 @@ public class AuthenticatedActionsHandler implements HttpHandler {
     @Override
     public void handleRequest(HttpServerExchange exchange) throws Exception {
         log.debugv("AuthenticatedActionsValve.invoke {0}", exchange.getRequestURI());
-        KeycloakAuthenticatedSession session = getSkeletonKeySession(exchange);
-        if (corsRequest(exchange, session)) return;
+        KeycloakUndertowAccount account = getAccount(exchange);
+        if (corsRequest(exchange, account)) return;
         String requestUri = exchange.getRequestURI();
         if (requestUri.endsWith(AdapterConstants.K_QUERY_BEARER_TOKEN)) {
-            queryBearerToken(exchange, session);
+            queryBearerToken(exchange, account);
             return;
         }
         next.handleRequest(exchange);
     }
 
-    public KeycloakAuthenticatedSession getSkeletonKeySession(HttpServerExchange exchange) {
-        KeycloakAuthenticatedSession skSession = exchange.getAttachment(KeycloakAuthenticationMechanism.SKELETON_KEY_SESSION_ATTACHMENT_KEY);
-        if (skSession != null) return skSession;
-        return null;
+    public KeycloakUndertowAccount getAccount(HttpServerExchange exchange) {
+        return (KeycloakUndertowAccount)exchange.getSecurityContext().getAuthenticatedAccount();
     }
 
-    protected void queryBearerToken(HttpServerExchange exchange, KeycloakAuthenticatedSession session) throws IOException, ServletException {
+    protected void queryBearerToken(HttpServerExchange exchange, KeycloakUndertowAccount account) throws IOException, ServletException {
         log.debugv("queryBearerToken {0}",exchange.getRequestURI());
-        if (abortTokenResponse(exchange, session)) return;
+        if (abortTokenResponse(exchange, account)) return;
         exchange.setResponseCode(StatusCodes.OK);
         exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, "text/plain");
-        exchange.getResponseSender().send(session.getTokenString());
+        exchange.getResponseSender().send(account.getEncodedAccessToken());
         exchange.endExchange();
     }
 
-    protected boolean abortTokenResponse(HttpServerExchange exchange, KeycloakAuthenticatedSession session) throws IOException {
-        if (session == null) {
-            log.debugv("session was null, sending back 401: {0}",exchange.getRequestURI());
+    protected boolean abortTokenResponse(HttpServerExchange exchange, KeycloakUndertowAccount account) throws IOException {
+        if (account == null) {
+            log.debugv("Not logged in, sending back 401: {0}",exchange.getRequestURI());
             exchange.setResponseCode(StatusCodes.UNAUTHORIZED);
             exchange.endExchange();
             return true;
@@ -75,6 +87,7 @@ public class AuthenticatedActionsHandler implements HttpHandler {
             exchange.endExchange();
             return true;
         }
+        // Don't allow a CORS request if we're not validating CORS requests.
         if (!adapterConfig.isCors() && exchange.getRequestHeaders().getFirst(Headers.ORIGIN) != null) {
             exchange.setResponseCode(StatusCodes.OK);
             exchange.endExchange();
@@ -83,13 +96,13 @@ public class AuthenticatedActionsHandler implements HttpHandler {
         return false;
     }
 
-    protected boolean corsRequest(HttpServerExchange exchange, KeycloakAuthenticatedSession session) throws IOException {
+    protected boolean corsRequest(HttpServerExchange exchange, KeycloakUndertowAccount account) throws IOException {
         if (!adapterConfig.isCors()) return false;
         log.debugv("CORS enabled + request.getRequestURI()");
         String origin = exchange.getRequestHeaders().getFirst("Origin");
         log.debugv("Origin: {0} uri: {1}", origin, exchange.getRequestURI());
-        if (session != null && origin != null) {
-            AccessToken token = session.getToken();
+        if (account != null && origin != null) {
+            AccessToken token = account.getAccessToken();
             Set<String> allowedOrigins = token.getAllowedOrigins();
             if (log.isDebugEnabled()) {
                 for (String a : allowedOrigins) log.debug("   " + a);
@@ -111,7 +124,7 @@ public class AuthenticatedActionsHandler implements HttpHandler {
             exchange.getResponseHeaders().put(PreflightCorsHandler.ACCESS_CONTROL_ALLOW_ORIGIN, origin);
             exchange.getResponseHeaders().put(PreflightCorsHandler.ACCESS_CONTROL_ALLOW_CREDENTIALS, "true");
         } else {
-            log.debugv("session or origin was null: {0}", exchange.getRequestURI());
+            log.debugv("not secured or origin was null: {0}", exchange.getRequestURI());
         }
         return false;
     }
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 a113150..33fd421 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
@@ -59,11 +59,7 @@ public class KeycloakAuthenticationMechanism implements AuthenticationMechanism 
             return AuthenticationMechanismOutcome.NOT_AUTHENTICATED;
         }
         else if (outcome == AuthenticationMechanismOutcome.AUTHENTICATED) {
-            final AccessToken token = bearer.getToken();
-            String surrogate = bearer.getSurrogate();
-            KeycloakAuthenticatedSession session = new KeycloakAuthenticatedSession(bearer.getTokenString(), token, resourceMetadata);
-            KeycloakPrincipal principal = completeAuthentication(securityContext, token, surrogate);
-            propagateBearer(exchange, session, principal);
+            completeAuthentication(securityContext, bearer);
             return AuthenticationMechanismOutcome.AUTHENTICATED;
         }
         else if (adapterConfig.isBearerOnly()) {
@@ -82,9 +78,7 @@ public class KeycloakAuthenticationMechanism implements AuthenticationMechanism 
             return AuthenticationMechanismOutcome.NOT_ATTEMPTED;
 
         }
-        KeycloakAuthenticatedSession session = new KeycloakAuthenticatedSession(oauth.getTokenString(), oauth.getToken(), resourceMetadata);
-        KeycloakPrincipal principal = completeAuthentication(securityContext, oauth.getToken(), null);
-        propagateOauth(exchange, session, principal);
+        completeAuthentication(exchange, securityContext, oauth);
         log.info("AUTHENTICATED");
         return AuthenticationMechanismOutcome.AUTHENTICATED;
     }
@@ -97,42 +91,23 @@ public class KeycloakAuthenticationMechanism implements AuthenticationMechanism 
         return new BearerTokenAuthenticator(resourceMetadata, adapterConfig.isUseResourceRoleMappings());
     }
 
-    protected KeycloakPrincipal completeAuthentication(SecurityContext securityContext, AccessToken token, String surrogate) {
-        final KeycloakPrincipal skeletonKeyPrincipal = new KeycloakPrincipal(token.getSubject(), surrogate);
-        Set<String> roles = null;
-        if (adapterConfig.isUseResourceRoleMappings()) {
-            AccessToken.Access access = token.getResourceAccess(resourceMetadata.getResourceName());
-            if (access != null) roles = access.getRoles();
-        } else {
-            AccessToken.Access access = token.getRealmAccess();
-            if (access != null) roles = access.getRoles();
-        }
-        if (roles == null) roles = Collections.emptySet();
-        final Set<String> accountRoles = roles;
-        Account account = new Account() {
-            @Override
-            public Principal getPrincipal() {
-                return skeletonKeyPrincipal;
-            }
-
-            @Override
-            public Set<String> getRoles() {
-                return accountRoles;
-            }
-        };
+    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);
         securityContext.authenticationComplete(account, "KEYCLOAK", true);
-        return skeletonKeyPrincipal;
+        login(exchange, account);
     }
 
-    protected void propagateBearer(HttpServerExchange exchange, KeycloakAuthenticatedSession session, KeycloakPrincipal principal) {
-        exchange.putAttachment(SKELETON_KEY_SESSION_ATTACHMENT_KEY, session);
-
+    protected void login(HttpServerExchange exchange, KeycloakUndertowAccount account) {
+        // complete
     }
 
-    protected void propagateOauth(HttpServerExchange exchange, KeycloakAuthenticatedSession session, KeycloakPrincipal principal) {
-        exchange.putAttachment(SKELETON_KEY_SESSION_ATTACHMENT_KEY, session);
-    }
 
+    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);
+        securityContext.authenticationComplete(account, "KEYCLOAK", false);
+    }
 
     @Override
     public ChallengeResult sendChallenge(HttpServerExchange exchange, SecurityContext securityContext) {
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
new file mode 100755
index 0000000..00d4a8f
--- /dev/null
+++ b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/KeycloakIdentityManager.java
@@ -0,0 +1,51 @@
+package org.keycloak.adapters.undertow;
+
+import io.undertow.security.idm.Account;
+import io.undertow.security.idm.Credential;
+import io.undertow.security.idm.IdentityManager;
+import io.undertow.util.StatusCodes;
+import org.jboss.logging.Logger;
+import org.keycloak.KeycloakPrincipal;
+import org.keycloak.RSATokenVerifier;
+import org.keycloak.VerificationException;
+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;
+
+/**
+* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+* @version $Revision: 1 $
+*/
+class KeycloakIdentityManager implements IdentityManager {
+    protected static Logger log = Logger.getLogger(KeycloakIdentityManager.class);
+
+    @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;
+        return account;
+    }
+
+    @Override
+    public Account verify(String id, Credential credential) {
+        KeycloakServletExtension.log.warn("Shouldn't call verify!!!");
+        throw new IllegalStateException("Not allowed");
+    }
+
+    @Override
+    public Account verify(Credential credential) {
+        KeycloakServletExtension.log.warn("Shouldn't call verify!!!");
+        throw new IllegalStateException("Not allowed");
+    }
+}
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 04e0d64..c7948ee 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
@@ -28,7 +28,7 @@ import java.util.Map;
  */
 public class KeycloakServletExtension implements ServletExtension {
 
-    protected Logger log = Logger.getLogger(KeycloakServletExtension.class);
+    protected static Logger log = Logger.getLogger(KeycloakServletExtension.class);
 
     // todo when this DeploymentInfo method of the same name is fixed.
     public boolean isAuthenticationMechanismPresent(DeploymentInfo deploymentInfo, final String mechanismName) {
@@ -79,7 +79,7 @@ public class KeycloakServletExtension implements ServletExtension {
                 realmConfiguration,
                 deploymentInfo.getConfidentialPortManager());
         }
-        ServletAuthenticatedActionsHandler.Wrapper actions = new ServletAuthenticatedActionsHandler.Wrapper(keycloakConfig);
+        AuthenticatedActionsHandler.Wrapper actions = new AuthenticatedActionsHandler.Wrapper(keycloakConfig);
 
         // setup handlers
 
@@ -95,25 +95,7 @@ public class KeycloakServletExtension implements ServletExtension {
         deploymentInfo.addInnerHandlerChainWrapper(ServletPropagateSessionHandler.WRAPPER); // propagates SkeletonKeySession
         deploymentInfo.addInnerHandlerChainWrapper(actions); // handles authenticated actions and cors.
 
-        deploymentInfo.setIdentityManager(new IdentityManager() {
-            @Override
-            public Account verify(Account account) {
-                log.info("Verifying account in IdentityManager");
-                return account;
-            }
-
-            @Override
-            public Account verify(String id, Credential credential) {
-                log.warn("Shouldn't call verify!!!");
-                throw new IllegalStateException("Not allowed");
-            }
-
-            @Override
-            public Account verify(Credential credential) {
-                log.warn("Shouldn't call verify!!!");
-                throw new IllegalStateException("Not allowed");
-            }
-        });
+        deploymentInfo.setIdentityManager(new KeycloakIdentityManager());
 
         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
new file mode 100755
index 0000000..dd2b174
--- /dev/null
+++ b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/KeycloakUndertowAccount.java
@@ -0,0 +1,115 @@
+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.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.security.Principal;
+import java.util.Collections;
+import java.util.Set;
+
+/**
+* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+* @version $Revision: 1 $
+*/
+public class KeycloakUndertowAccount implements Account {
+    protected static Logger log = Logger.getLogger(KeycloakUndertowAccount.class);
+    protected AccessToken accessToken;
+    protected String encodedAccessToken;
+    protected String refreshToken;
+    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) {
+        this.principal = principal;
+        this.accessToken = accessToken;
+        this.encodedAccessToken = encodedAccessToken;
+        this.refreshToken = refreshToken;
+        this.realmConfiguration = realmConfiguration;
+        this.resourceMetadata = resourceMetadata;
+        this.adapterConfig = adapterConfig;
+        setRoles(accessToken);
+    }
+
+    protected void setRoles(AccessToken accessToken) {
+        Set<String> roles = null;
+        if (adapterConfig.isUseResourceRoleMappings()) {
+            AccessToken.Access access = accessToken.getResourceAccess(resourceMetadata.getResourceName());
+            if (access != null) roles = access.getRoles();
+        } else {
+            AccessToken.Access access = accessToken.getRealmAccess();
+            if (access != null) roles = access.getRoles();
+        }
+        if (roles == null) roles = Collections.emptySet();
+        this.accountRoles = roles;
+    }
+
+    @Override
+    public Principal getPrincipal() {
+        return principal;
+    }
+
+    @Override
+    public Set<String> getRoles() {
+        return accountRoles;
+    }
+
+    public AccessToken getAccessToken() {
+        return accessToken;
+    }
+
+    public String getEncodedAccessToken() {
+        return encodedAccessToken;
+    }
+
+    public String getRefreshToken() {
+        return refreshToken;
+    }
+
+    public ResourceMetadata getResourceMetadata() {
+        return resourceMetadata;
+    }
+
+    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);
+
+    }
+}
diff --git a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/OAuthAuthenticator.java b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/OAuthAuthenticator.java
index 6205e17..d21e5d3 100755
--- a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/OAuthAuthenticator.java
+++ b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/OAuthAuthenticator.java
@@ -34,6 +34,7 @@ public class OAuthAuthenticator {
     protected AccessToken token;
     protected HttpServerExchange exchange;
     protected KeycloakChallenge challenge;
+    protected String refreshToken;
 
     public OAuthAuthenticator(HttpServerExchange exchange, RealmConfiguration realmInfo,  int sslRedirectPort) {
         this.exchange = exchange;
@@ -53,6 +54,10 @@ public class OAuthAuthenticator {
         return token;
     }
 
+    public String getRefreshToken() {
+        return refreshToken;
+    }
+
     protected String getRequestUrl() {
         KeycloakUriBuilder uriBuilder = KeycloakUriBuilder.fromUri(exchange.getRequestURI())
                 .replaceQuery(exchange.getQueryString());
@@ -234,7 +239,7 @@ public class OAuthAuthenticator {
         AccessTokenResponse tokenResponse = null;
         String redirectUri = stripOauthParametersFromRedirect();
         try {
-            tokenResponse = TokenGrantRequest.invoke(realmInfo, code, redirectUri);
+            tokenResponse = TokenGrantRequest.invokeAccessCodeToToken(realmInfo, code, redirectUri);
         } catch (TokenGrantRequest.HttpFailure failure) {
             log.error("failed to turn code into token");
             log.error("status from server: " + failure.getStatus());
@@ -249,6 +254,7 @@ public class OAuthAuthenticator {
         }
 
         tokenString = tokenResponse.getToken();
+        refreshToken = tokenResponse.getRefreshToken();
         try {
             token = RSATokenVerifier.verifyToken(tokenString, realmInfo.getMetadata().getRealmKey(), realmInfo.getMetadata().getRealm());
             log.debug("Token Verification succeeded!");
diff --git a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletAdminActionsHandler.java b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletAdminActionsHandler.java
index 8dcef64..75a9414 100755
--- a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletAdminActionsHandler.java
+++ b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletAdminActionsHandler.java
@@ -84,6 +84,7 @@ public class ServletAdminActionsHandler implements HttpHandler {
         SessionManager manager = servletRequestContext.getDeployment().getSessionManager();
         String requestUri = exchange.getRequestURI();
         if (requestUri.endsWith(AdapterConstants.K_LOGOUT)) {
+            log.info("K_LOGOUT sent");
             JWSInput token = verifyAdminRequest(request, response);
             if (token == null) return;
             userSessionManagement.remoteLogout(token, manager, response);
diff --git a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletKeycloakAuthenticationMechanism.java b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletKeycloakAuthenticationMechanism.java
index 0645084..421986c 100755
--- a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletKeycloakAuthenticationMechanism.java
+++ b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletKeycloakAuthenticationMechanism.java
@@ -29,7 +29,6 @@ public class ServletKeycloakAuthenticationMechanism extends KeycloakAuthenticati
     public ServletKeycloakAuthenticationMechanism(AdapterConfig config, ResourceMetadata metadata, ConfidentialPortManager portManager) {
         super(config, metadata);
         this.portManager = portManager;
-        this.userSessionManagement = userSessionManagement;
     }
 
 
@@ -39,21 +38,12 @@ public class ServletKeycloakAuthenticationMechanism extends KeycloakAuthenticati
     }
 
     @Override
-    protected void propagateBearer(HttpServerExchange exchange, KeycloakAuthenticatedSession skSession, KeycloakPrincipal principal) {
-        super.propagateBearer(exchange, skSession, principal);
-        final ServletRequestContext servletRequestContext = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY);
-        HttpServletRequest req = (HttpServletRequest) servletRequestContext.getServletRequest();
-        req.setAttribute(KeycloakAuthenticatedSession.class.getName(), skSession);
-    }
-
-    @Override
-    protected void propagateOauth(HttpServerExchange exchange, KeycloakAuthenticatedSession skSession, KeycloakPrincipal principal) {
-        super.propagateBearer(exchange, skSession, principal);
+    protected void login(HttpServerExchange exchange, KeycloakUndertowAccount account) {
         final ServletRequestContext servletRequestContext = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY);
         HttpServletRequest req = (HttpServletRequest) servletRequestContext.getServletRequest();
-        req.setAttribute(KeycloakAuthenticatedSession.class.getName(), skSession);
         HttpSession session = req.getSession(true);
-        session.setAttribute(KeycloakAuthenticatedSession.class.getName(), skSession);
-        userSessionManagement.login(servletRequestContext.getDeployment().getSessionManager(), session, principal.getName());
+        userSessionManagement.login(servletRequestContext.getDeployment().getSessionManager(), session, account.getPrincipal().getName());
+
     }
+
 }
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 608f867..9baa829 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
@@ -33,31 +33,27 @@ public class ServletPropagateSessionHandler implements HttpHandler {
 
     @Override
     public void handleRequest(HttpServerExchange exchange) throws Exception {
-        log.info("handleRequest");
-        final ServletRequestContext servletRequestContext = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY);
-        HttpServletRequest req = (HttpServletRequest) servletRequestContext.getServletRequest();
-        KeycloakAuthenticatedSession skSession = (KeycloakAuthenticatedSession)req.getAttribute(KeycloakAuthenticatedSession.class.getName());
-        if (skSession != null) {
-            log.info("skSession is in request");
+        log.debug("handleRequest");
+        KeycloakUndertowAccount account = (KeycloakUndertowAccount)exchange.getSecurityContext().getAuthenticatedAccount();
+        if (account == null) {
+            log.debug("Not logged in, nothing to propagate");
             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);
 
         HttpSession session = req.getSession(false);
         if (session == null) {
-            log.info("http session was null, nothing to propagate");
             next.handleRequest(exchange);
             return;
         }
-        skSession = (KeycloakAuthenticatedSession)session.getAttribute(KeycloakAuthenticatedSession.class.getName());
-        if (skSession == null) {
-            log.info("skSession not in http session, nothing to propagate");
-            next.handleRequest(exchange);
-            return;
-        }
-        log.info("propagating");
-        req.setAttribute(KeycloakAuthenticatedSession.class.getName(), skSession);
-        exchange.putAttachment(KeycloakAuthenticationMechanism.SKELETON_KEY_SESSION_ATTACHMENT_KEY, skSession);
+        log.debug("propagating to HTTP Session");
+        session.setAttribute(KeycloakAuthenticatedSession.class.getName(), skSession);
         next.handleRequest(exchange);
     }
 }
diff --git a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowKeycloakSession.java b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowKeycloakSession.java
new file mode 100755
index 0000000..22f79d9
--- /dev/null
+++ b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowKeycloakSession.java
@@ -0,0 +1,43 @@
+package org.keycloak.adapters.undertow;
+
+import org.keycloak.KeycloakAuthenticatedSession;
+import org.keycloak.adapters.ResourceMetadata;
+import org.keycloak.representations.AccessToken;
+
+import java.io.Serializable;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class UndertowKeycloakSession extends KeycloakAuthenticatedSession {
+
+    private transient KeycloakUndertowAccount account;
+
+    public UndertowKeycloakSession(KeycloakUndertowAccount account) {
+        super(account.getEncodedAccessToken(), account.getAccessToken(), account.getResourceMetadata());
+        this.account = account;
+    }
+
+    @Override
+    public AccessToken getToken() {
+        checkExpiration();
+        return super.getToken();
+    }
+
+    private void checkExpiration() {
+        if (token.isExpired() && account != null) {
+            account.refreshExpiredToken();
+            this.token = account.getAccessToken();
+            this.tokenString = account.getEncodedAccessToken();
+
+        }
+    }
+
+    @Override
+    public String getTokenString() {
+        checkExpiration();
+        return super.getTokenString();
+    }
+
+}
diff --git a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UserSessionManagement.java b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UserSessionManagement.java
index 1fe98bc..4cbd1d1 100755
--- a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UserSessionManagement.java
+++ b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UserSessionManagement.java
@@ -41,7 +41,7 @@ public class UserSessionManagement implements SessionListener {
 
     public void remoteLogout(JWSInput token, SessionManager manager, HttpServletResponse response) throws IOException {
         try {
-            log.debug("->> remoteLogout: ");
+            log.info("->> remoteLogout: ");
             LogoutAction action = JsonSerialization.readValue(token.getContent(), LogoutAction.class);
             if (action.isExpired()) {
                 log.warn("admin request failed, expired token");
@@ -56,10 +56,10 @@ public class UserSessionManagement implements SessionListener {
             }
             String user = action.getUser();
             if (user != null) {
-                log.debug("logout of session for: " + user);
+                log.info("logout of session for: " + user);
                 logout(manager, user);
             } else {
-                log.debug("logout of all sessions");
+                log.info("logout of all sessions");
                 logoutAll(manager);
             }
         } catch (Exception e) {
@@ -118,13 +118,13 @@ public class UserSessionManagement implements SessionListener {
     }
 
     public void logout(SessionManager manager, String user) {
-        log.debug("logoutUser: " + user);
+        log.info("logoutUser: " + user);
         Set<String> map = userSessionMap.remove(user);
         if (map == null) {
-            log.debug("no session for user: " + user);
+            log.info("no session for user: " + user);
             return;
         }
-        log.debug("found session for user");
+        log.info("found session for user");
         synchronized (map) {
             for (String id : map) {
                 log.debug("invalidating session for user: " + user);
diff --git a/model/api/src/main/java/org/keycloak/models/RealmModel.java b/model/api/src/main/java/org/keycloak/models/RealmModel.java
index a0aad37..b5682ee 100755
--- a/model/api/src/main/java/org/keycloak/models/RealmModel.java
+++ b/model/api/src/main/java/org/keycloak/models/RealmModel.java
@@ -185,4 +185,6 @@ public interface RealmModel extends RoleContainerModel, RoleMapperModel, ScopeMa
      * @return
      */
     UserCredentialModel getSecret(UserModel user);
+
+    boolean hasScope(UserModel user, RoleModel role);
 }
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
index 46e2b7d..dfa3800 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
@@ -903,6 +903,19 @@ public class RealmAdapter implements RealmModel {
         return false;
     }
 
+    @Override
+    public boolean hasScope(UserModel user, RoleModel role) {
+        Set<RoleModel> roles = getScopeMappings(user);
+        if (roles.contains(role)) return true;
+
+        for (RoleModel mapping : roles) {
+            if (mapping.hasRole(role)) return true;
+        }
+        return false;
+    }
+
+
+
     protected TypedQuery<UserRoleMappingEntity> getUserRoleMappingEntityTypedQuery(UserAdapter user, RoleAdapter role) {
         TypedQuery<UserRoleMappingEntity> query = em.createNamedQuery("userHasRole", UserRoleMappingEntity.class);
         query.setParameter("user", user.getUser());
@@ -1009,12 +1022,6 @@ public class RealmAdapter implements RealmModel {
         }
     }
 
-    public boolean hasScope(UserModel user, RoleModel role) {
-        TypedQuery<UserScopeMappingEntity> query = getRealmScopeMappingQuery((UserAdapter) user, (RoleAdapter) role);
-        return query.getResultList().size() > 0;
-    }
-
-
     protected TypedQuery<UserScopeMappingEntity> getRealmScopeMappingQuery(UserAdapter user, RoleAdapter role) {
         TypedQuery<UserScopeMappingEntity> query = em.createNamedQuery("userHasScope", UserScopeMappingEntity.class);
         query.setParameter("user", ((UserAdapter)user).getUser());
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java
index 758c2cc..6e8cd8c 100755
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java
@@ -615,6 +615,18 @@ public class RealmAdapter extends AbstractAdapter implements RealmModel {
     }
 
     @Override
+    public boolean hasScope(UserModel user, RoleModel role) {
+        Set<RoleModel> roles = getScopeMappings(user);
+        if (roles.contains(role)) return true;
+
+        for (RoleModel mapping : roles) {
+            if (mapping.hasRole(role)) return true;
+        }
+        return false;
+    }
+
+
+    @Override
     public void addScopeMapping(UserModel agent, RoleModel role) {
         UserEntity userEntity = ((UserAdapter)agent).getUser();
         getMongoStore().pushItemToList(userEntity, "scopeIds", role.getId(), true, invocationContext);
diff --git a/services/src/main/java/org/keycloak/services/managers/ApplianceBootstrap.java b/services/src/main/java/org/keycloak/services/managers/ApplianceBootstrap.java
index 93a8e47..ac95546 100755
--- a/services/src/main/java/org/keycloak/services/managers/ApplianceBootstrap.java
+++ b/services/src/main/java/org/keycloak/services/managers/ApplianceBootstrap.java
@@ -46,7 +46,8 @@ public class ApplianceBootstrap {
         realm.addRequiredCredential(CredentialRepresentation.PASSWORD);
         realm.addRequiredOAuthClientCredential(CredentialRepresentation.PASSWORD);
         realm.addRequiredResourceCredential(CredentialRepresentation.PASSWORD);
-        realm.setAccessTokenLifespan(300);
+        realm.setAccessTokenLifespan(60);
+        realm.setRefreshTokenLifespan(3600);
         realm.setAccessCodeLifespan(60);
         realm.setAccessCodeLifespanUserAction(300);
         realm.setSslNotRequired(true);
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 14adb6b..d8f4911 100755
--- a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
@@ -36,7 +36,7 @@ import java.util.Set;
  * @version $Revision: 1 $
  */
 public class AuthenticationManager {
-    protected Logger logger = Logger.getLogger(AuthenticationManager.class);
+    protected static Logger logger = Logger.getLogger(AuthenticationManager.class);
     public static final String FORM_USERNAME = "username";
     public static final String KEYCLOAK_IDENTITY_COOKIE = "KEYCLOAK_IDENTITY";
 
@@ -127,20 +127,26 @@ public class AuthenticationManager {
     }
 
     public UserModel authenticateIdentityCookie(RealmModel realm, UriInfo uriInfo, HttpHeaders headers) {
+        return authenticateIdentityCookie(realm, uriInfo, headers, true);
+    }
+
+
+    public UserModel authenticateIdentityCookie(RealmModel realm, UriInfo uriInfo, HttpHeaders headers, boolean checkActive) {
+        logger.info("authenticateIdentityCookie");
         String cookieName = KEYCLOAK_IDENTITY_COOKIE;
-        Auth auth = authenticateIdentityCookie(realm, uriInfo, headers, cookieName);
+        Auth auth = authenticateIdentityCookie(realm, uriInfo, headers, cookieName, checkActive);
         return auth != null ? auth.getUser() : null;
     }
 
     public UserModel authenticateSaasIdentityCookie(RealmModel realm, UriInfo uriInfo, HttpHeaders headers) {
         String cookieName = AdminService.SAAS_IDENTITY_COOKIE;
-        Auth auth = authenticateIdentityCookie(realm, uriInfo, headers, cookieName);
+        Auth auth = authenticateIdentityCookie(realm, uriInfo, headers, cookieName, true);
         return auth != null ? auth.getUser() : null;
     }
 
     public Auth authenticateAccountIdentityCookie(RealmModel realm, UriInfo uriInfo, HttpHeaders headers) {
         String cookieName = AccountService.ACCOUNT_IDENTITY_COOKIE;
-        return authenticateIdentityCookie(realm, uriInfo, headers, cookieName);
+        return authenticateIdentityCookie(realm, uriInfo, headers, cookieName, true);
     }
 
     public UserModel authenticateSaasIdentity(RealmModel realm, UriInfo uriInfo, HttpHeaders headers) {
@@ -159,18 +165,20 @@ public class AuthenticationManager {
     }
 
 
-    protected Auth authenticateIdentityCookie(RealmModel realm, UriInfo uriInfo, HttpHeaders headers, String cookieName) {
+    protected Auth authenticateIdentityCookie(RealmModel realm, UriInfo uriInfo, HttpHeaders headers, String cookieName, boolean checkActive) {
+        logger.info("authenticateIdentityCookie");
         Cookie cookie = headers.getCookies().get(cookieName);
         if (cookie == null) {
-            logger.debug("authenticateCookie could not find cookie: {0}", cookieName);
+            logger.info("authenticateCookie could not find cookie: {0}", cookieName);
             return null;
         }
 
         String tokenString = cookie.getValue();
         try {
-            AccessToken token = RSATokenVerifier.verifyToken(tokenString, realm.getPublicKey(), realm.getName());
-            if (!token.isActive()) {
-                logger.debug("identity cookie expired");
+            AccessToken token = RSATokenVerifier.verifyToken(tokenString, realm.getPublicKey(), realm.getName(), checkActive);
+            logger.info("identity token verified");
+            if (checkActive && !token.isActive()) {
+                logger.info("identity cookie expired");
                 expireIdentityCookie(realm, uriInfo);
                 return null;
             }
@@ -179,7 +187,7 @@ public class AuthenticationManager {
 
             UserModel user = realm.getUserById(token.getSubject());
             if (user == null || !user.isEnabled()) {
-                logger.debug("Unknown user in identity cookie");
+                logger.info("Unknown user in identity cookie");
                 expireIdentityCookie(realm, uriInfo);
                 return null;
             }
@@ -188,7 +196,7 @@ public class AuthenticationManager {
             if (token.getIssuedFor() != null) {
                 UserModel client = realm.getUser(token.getIssuedFor());
                 if (client == null || !client.isEnabled()) {
-                    logger.debug("Unknown client in identity cookie");
+                    logger.info("Unknown client in identity cookie");
                     expireIdentityCookie(realm, uriInfo);
                     return null;
                 }
@@ -197,7 +205,7 @@ public class AuthenticationManager {
 
             return auth;
         } catch (VerificationException e) {
-            logger.debug("Failed to verify identity cookie", e);
+            logger.info("Failed to verify identity cookie", e);
             expireCookie(cookie.getName(), cookie.getPath());
         }
         return null;
diff --git a/services/src/main/java/org/keycloak/services/managers/ModelToRepresentation.java b/services/src/main/java/org/keycloak/services/managers/ModelToRepresentation.java
index c9e921b..afceb25 100755
--- a/services/src/main/java/org/keycloak/services/managers/ModelToRepresentation.java
+++ b/services/src/main/java/org/keycloak/services/managers/ModelToRepresentation.java
@@ -72,6 +72,7 @@ public class ModelToRepresentation {
         rep.setVerifyEmail(realm.isVerifyEmail());
         rep.setResetPasswordAllowed(realm.isResetPasswordAllowed());
         rep.setAccessTokenLifespan(realm.getAccessTokenLifespan());
+        rep.setRefreshTokenLifespan(realm.getRefreshTokenLifespan());
         rep.setAccessCodeLifespan(realm.getAccessCodeLifespan());
         rep.setAccessCodeLifespanUserAction(realm.getAccessCodeLifespanUserAction());
         rep.setSmtpServer(realm.getSmtpConfig());
diff --git a/services/src/main/java/org/keycloak/services/managers/RealmManager.java b/services/src/main/java/org/keycloak/services/managers/RealmManager.java
index 4148ca0..d364d5b 100755
--- a/services/src/main/java/org/keycloak/services/managers/RealmManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/RealmManager.java
@@ -106,6 +106,7 @@ public class RealmManager {
         if (rep.getAccessCodeLifespanUserAction() != null)
             realm.setAccessCodeLifespanUserAction(rep.getAccessCodeLifespanUserAction());
         if (rep.getAccessTokenLifespan() != null) realm.setAccessTokenLifespan(rep.getAccessTokenLifespan());
+        if (rep.getRefreshTokenLifespan() != null) realm.setRefreshTokenLifespan(rep.getRefreshTokenLifespan());
         if (rep.getRequiredCredentials() != null) {
             realm.updateRequiredCredentials(rep.getRequiredCredentials());
         }
@@ -161,6 +162,9 @@ public class RealmManager {
         if (rep.getAccessTokenLifespan() != null) newRealm.setAccessTokenLifespan(rep.getAccessTokenLifespan());
         else newRealm.setAccessTokenLifespan(300);
 
+        if (rep.getRefreshTokenLifespan() != null) newRealm.setRefreshTokenLifespan(rep.getRefreshTokenLifespan());
+        else newRealm.setRefreshTokenLifespan(3600);
+
         if (rep.getAccessCodeLifespan() != null) newRealm.setAccessCodeLifespan(rep.getAccessCodeLifespan());
         else newRealm.setAccessCodeLifespan(60);
 
diff --git a/services/src/main/java/org/keycloak/services/managers/ResourceAdminManager.java b/services/src/main/java/org/keycloak/services/managers/ResourceAdminManager.java
index 345deec..76607db 100755
--- a/services/src/main/java/org/keycloak/services/managers/ResourceAdminManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/ResourceAdminManager.java
@@ -18,7 +18,7 @@ import java.util.List;
  * @version $Revision: 1 $
  */
 public class ResourceAdminManager {
-    protected Logger logger = Logger.getLogger(ResourceAdminManager.class);
+    protected static Logger logger = Logger.getLogger(ResourceAdminManager.class);
 
     public void logoutAll(RealmModel realm) {
         singleLogOut(realm, null);
@@ -41,12 +41,14 @@ public class ResourceAdminManager {
         if (managementUrl != null) {
             LogoutAction adminAction = new LogoutAction(TokenIdGenerator.generateId(), System.currentTimeMillis() / 1000 + 30, resource.getName(), user);
             String token = new TokenManager().encodeToken(realm, adminAction);
-            logger.debug("logout user: {0} resource: {1} url: {2}", user, resource.getName(), managementUrl);
+            logger.info("logout user: {0} resource: {1} url: {2}", user, resource.getName(), managementUrl);
             Response response = client.target(managementUrl).path(AdapterConstants.K_LOGOUT).request().post(Entity.text(token));
             boolean success = response.getStatus() == 204;
             response.close();
+            logger.info("logout success.");
             return success;
         } else {
+            logger.info("logout failure.");
             return false;
         }
     }
diff --git a/services/src/main/java/org/keycloak/services/managers/TokenManager.java b/services/src/main/java/org/keycloak/services/managers/TokenManager.java
index 74b5bd4..080d52f 100755
--- a/services/src/main/java/org/keycloak/services/managers/TokenManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/TokenManager.java
@@ -1,7 +1,10 @@
 package org.keycloak.services.managers;
 
 import org.jboss.resteasy.logging.Logger;
+import org.keycloak.OAuthErrorException;
 import org.keycloak.jose.jws.JWSBuilder;
+import org.keycloak.jose.jws.JWSInput;
+import org.keycloak.jose.jws.crypto.RSAProvider;
 import org.keycloak.models.ApplicationModel;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.RoleModel;
@@ -9,6 +12,8 @@ import org.keycloak.models.UserModel;
 import org.keycloak.models.utils.KeycloakModelUtils;
 import org.keycloak.representations.AccessScope;
 import org.keycloak.representations.AccessToken;
+import org.keycloak.representations.AccessTokenResponse;
+import org.keycloak.representations.RefreshToken;
 import org.keycloak.util.Base64Url;
 import org.keycloak.util.JsonSerialization;
 
@@ -16,6 +21,7 @@ import javax.ws.rs.core.MultivaluedHashMap;
 import javax.ws.rs.core.MultivaluedMap;
 import java.io.IOException;
 import java.io.UnsupportedEncodingException;
+import java.security.PrivateKey;
 import java.util.HashSet;
 import java.util.LinkedList;
 import java.util.List;
@@ -109,6 +115,75 @@ public class TokenManager {
         return code;
     }
 
+    public AccessToken refreshAccessToken(RealmModel realm, UserModel client, String encodedRefreshToken) throws OAuthErrorException {
+        JWSInput jws = new JWSInput(encodedRefreshToken);
+        RefreshToken refreshToken = null;
+        try {
+            if (!RSAProvider.verify(jws, realm.getPublicKey())) {
+                throw new RuntimeException("Invalid refresh token");
+            }
+            refreshToken = jws.readJsonContent(RefreshToken.class);
+        } catch (IOException e) {
+            throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Invalid refresh token", e);
+        }
+        if (refreshToken.isExpired()) {
+            throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Refresh token expired");
+        }
+
+        UserModel user = realm.getUserById(refreshToken.getSubject());
+        if (user == null) {
+            throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Invalid refresh token", "Unknown user");
+        }
+
+        if (!user.isEnabled()) {
+            throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "User disabled", "User disabled");
+
+        }
+
+        ApplicationModel clientApp = realm.getApplicationByName(client.getLoginName());
+
+
+        if (refreshToken.getRealmAccess() != null) {
+            for (String roleName : refreshToken.getRealmAccess().getRoles()) {
+                RoleModel role = realm.getRole(roleName);
+                if (role == null) {
+                    throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Invalid realm role " + roleName);
+                }
+                if (!realm.hasRole(user, role)) {
+                    throw new OAuthErrorException(OAuthErrorException.INVALID_SCOPE, "User no long has permission for realm role: " + roleName);
+                }
+                if (!realm.hasScope(client, role)) {
+                    throw new OAuthErrorException(OAuthErrorException.INVALID_SCOPE, "Client no longer has realm scope: " + roleName);
+                }
+            }
+        }
+        if (refreshToken.getResourceAccess() != null) {
+            for (Map.Entry<String, AccessToken.Access> entry : refreshToken.getResourceAccess().entrySet()) {
+                ApplicationModel app = realm.getApplicationByName(entry.getKey());
+                if (app == null) {
+                    throw new OAuthErrorException(OAuthErrorException.INVALID_SCOPE, "Application no longer exists", "Application no longer exists: " + app.getName());
+                }
+                for (String roleName : refreshToken.getRealmAccess().getRoles()) {
+                    RoleModel role = app.getRole(roleName);
+                    if (role == null) {
+                        throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Invalid refresh token", "Unknown application role: " + roleName);
+                    }
+                    if (!realm.hasRole(user, role)) {
+                        throw new OAuthErrorException(OAuthErrorException.INVALID_SCOPE, "User no long has permission for application role " + roleName);
+                    }
+                    if (clientApp != null && !clientApp.equals(app) && !realm.hasScope(client, role)) {
+                        throw new OAuthErrorException(OAuthErrorException.INVALID_SCOPE, "Client no longer has application scope" + roleName);
+                    }
+                }
+
+            }
+        }
+        AccessToken accessToken = initToken(realm, client, user);
+        accessToken.setRealmAccess(refreshToken.getRealmAccess());
+        accessToken.setResourceAccess(refreshToken.getResourceAccess());
+        return accessToken;
+    }
+
     public AccessToken createClientAccessToken(String scopeParam, RealmModel realm, UserModel client, UserModel user) {
         return createClientAccessToken(scopeParam, realm, client, user, new LinkedList<RoleModel>(), new MultivaluedHashMap<String, RoleModel>());
     }
@@ -172,8 +247,10 @@ public class TokenManager {
         token.audience(realm.getName());
         token.issuedNow();
         token.issuedFor(client.getLoginName());
+        token.issuer(realm.getName());
         if (realm.getAccessTokenLifespan() > 0) {
             token.expiration((System.currentTimeMillis() / 1000) + realm.getAccessTokenLifespan());
+            logger.info("Access Token expiration: " + token.getExpiration());
         }
         Set<String> allowedOrigins = client.getWebOrigins();
         if (allowedOrigins != null) {
@@ -232,26 +309,68 @@ public class TokenManager {
     }
 
 
-    public AccessToken createAccessToken(RealmModel realm, UserModel user) {
-        AccessToken token = new AccessToken();
-        token.id(KeycloakModelUtils.generateId());
-        token.issuedNow();
-        token.subject(user.getId());
-        token.audience(realm.getName());
-        if (realm.getAccessTokenLifespan() > 0) {
-            token.expiration((System.currentTimeMillis() / 1000) + realm.getAccessTokenLifespan());
-        }
-        for (RoleModel role : realm.getRoleMappings(user)) {
-            addComposites(token, role);
-        }
-        return token;
-    }
-
-
     public String encodeToken(RealmModel realm, Object token) {
         String encodedToken = new JWSBuilder()
                 .jsonContent(token)
                 .rsa256(realm.getPrivateKey());
         return encodedToken;
     }
+
+    public AccessTokenResponseBuilder responseBuilder(RealmModel realm) {
+        return new AccessTokenResponseBuilder(realm);
+    }
+
+    public class AccessTokenResponseBuilder {
+        RealmModel realm;
+        AccessToken accessToken;
+        RefreshToken refreshToken;
+
+        public AccessTokenResponseBuilder(RealmModel realm) {
+            this.realm = realm;
+        }
+
+        public AccessTokenResponseBuilder accessToken(AccessToken accessToken) {
+            this.accessToken = accessToken;
+            return this;
+        }
+        public AccessTokenResponseBuilder refreshToken(RefreshToken refreshToken) {
+            this.refreshToken = refreshToken;
+            return this;
+        }
+
+        public AccessTokenResponseBuilder generateAccessToken(String scopeParam, UserModel client, UserModel user) {
+            accessToken = createClientAccessToken(scopeParam, realm, client, user);
+            return this;
+        }
+
+        public AccessTokenResponseBuilder generateRefreshToken() {
+            if (accessToken == null) {
+                throw new IllegalStateException("accessToken not set");
+            }
+            refreshToken = new RefreshToken(accessToken);
+            refreshToken.id(KeycloakModelUtils.generateId());
+            refreshToken.issuedNow();
+            refreshToken.expiration((System.currentTimeMillis() / 1000) + realm.getRefreshTokenLifespan());
+            return this;
+        }
+
+        public AccessTokenResponse build() {
+            AccessTokenResponse res = new AccessTokenResponse();
+            if (accessToken != null) {
+                String encodedToken = new JWSBuilder().jsonContent(accessToken).rsa256(realm.getPrivateKey());
+                res.setToken(encodedToken);
+                res.setTokenType("bearer");
+                if (accessToken.getExpiration() != 0) {
+                    long time = accessToken.getExpiration() - (System.currentTimeMillis() / 1000);
+                    res.setExpiresIn(time);
+                }
+            }
+            if (refreshToken != null) {
+                String encodedToken = new JWSBuilder().jsonContent(refreshToken).rsa256(realm.getPrivateKey());
+                res.setRefreshToken(encodedToken);
+            }
+            return res;
+        }
+    }
+
 }
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 f38c985..4983d37 100755
--- a/services/src/main/java/org/keycloak/services/resources/TokenService.java
+++ b/services/src/main/java/org/keycloak/services/resources/TokenService.java
@@ -4,6 +4,7 @@ import org.jboss.resteasy.annotations.cache.NoCache;
 import org.jboss.resteasy.logging.Logger;
 import org.jboss.resteasy.spi.HttpRequest;
 import org.jboss.resteasy.spi.HttpResponse;
+import org.keycloak.OAuthErrorException;
 import org.keycloak.jose.jws.JWSBuilder;
 import org.keycloak.jose.jws.JWSInput;
 import org.keycloak.jose.jws.crypto.RSAProvider;
@@ -156,9 +157,36 @@ public class TokenService {
             throw new NotAuthorizedException("Auth failed");
         }
         String scope = form.getFirst("scope");
-        AccessToken token = tokenManager.createClientAccessToken(scope, realm, client, user);
-        String encoded = tokenManager.encodeToken(realm, token);
-        AccessTokenResponse res = accessTokenResponse(token, encoded);
+        AccessTokenResponse res = tokenManager.responseBuilder(realm)
+                .generateAccessToken(scope, client, user).build();
+        return Response.ok(res, MediaType.APPLICATION_JSON_TYPE).build();
+    }
+
+    @Path("refresh")
+    @POST
+    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
+    @Produces(MediaType.APPLICATION_JSON)
+    public Response refreshAccessToken(final @HeaderParam(HttpHeaders.AUTHORIZATION) String authorizationHeader,
+                                     final MultivaluedMap<String, String> form) {
+        if (!checkSsl()) {
+            throw new NotAcceptableException("HTTPS required");
+        }
+
+        UserModel client = authorizeClient(authorizationHeader);
+        String refreshToken = form.getFirst("refresh_token");
+        AccessToken accessToken = null;
+        try {
+            accessToken = tokenManager.refreshAccessToken(realm, client, refreshToken);
+        } catch (OAuthErrorException e) {
+            Map<String, String> error = new HashMap<String, String>();
+            error.put("error", e.getError());
+            if (e.getDescription() != null) error.put("error_description", e.getDescription());
+            throw new BadRequestException(Response.status(Response.Status.BAD_REQUEST).entity(error).type("application/json").build(), e);
+        }
+
+        AccessTokenResponse res = tokenManager.responseBuilder(realm)
+                                              .accessToken(accessToken)
+                                              .generateRefreshToken().build();
         return Response.ok(res, MediaType.APPLICATION_JSON_TYPE).build();
     }
 
@@ -368,7 +396,9 @@ public class TokenService {
                     .build();
         }
         logger.debug("accessRequest SUCCESS");
-        AccessTokenResponse res = accessTokenResponse(realm.getPrivateKey(), accessCode.getToken());
+        AccessTokenResponse res = tokenManager.responseBuilder(realm)
+                                              .accessToken(accessCode.getToken())
+                                              .generateRefreshToken().build();
 
         return Cors.add(request, Response.ok(res)).allowedOrigins(client).allowedMethods("POST").build();
     }
@@ -408,23 +438,6 @@ public class TokenService {
         return client;
     }
 
-    protected AccessTokenResponse accessTokenResponse(PrivateKey privateKey, AccessToken token) {
-        String encodedToken = new JWSBuilder().jsonContent(token).rsa256(privateKey);
-
-        return accessTokenResponse(token, encodedToken);
-    }
-
-    protected AccessTokenResponse accessTokenResponse(AccessToken token, String encodedToken) {
-        AccessTokenResponse res = new AccessTokenResponse();
-        res.setToken(encodedToken);
-        res.setTokenType("bearer");
-        if (token.getExpiration() != 0) {
-            long time = token.getExpiration() - (System.currentTimeMillis() / 1000);
-            res.setExpiresIn(time);
-        }
-        return res;
-    }
-
     @Path("login")
     @GET
     public Response loginPage(final @QueryParam("response_type") String responseType,
@@ -526,11 +539,14 @@ public class TokenService {
     public Response logout(final @QueryParam("redirect_uri") String redirectUri) {
         // todo do we care if anybody can trigger this?
 
-        UserModel user = authManager.authenticateIdentityCookie(realm, uriInfo, headers);
+        // authenticate identity cookie, but ignore an access token timeout as we're logging out anyways.
+        UserModel user = authManager.authenticateIdentityCookie(realm, uriInfo, headers, false);
         if (user != null) {
-            logger.debug("Logging out: {0}", user.getLoginName());
+            logger.info("Logging out: {0}", user.getLoginName());
             authManager.expireIdentityCookie(realm, uriInfo);
-            resourceAdminManager.singleLogOut(realm, user.getLoginName());
+            resourceAdminManager.singleLogOut(realm, user.getId());
+        } else {
+            logger.info("No user logged in for logout");
         }
         // todo manage legal redirects
         return Response.status(302).location(UriBuilder.fromUri(redirectUri).build()).build();
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/composites/CompositeRoleTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/composites/CompositeRoleTest.java
index 720ea52..335bef2 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/composites/CompositeRoleTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/composites/CompositeRoleTest.java
@@ -58,6 +58,7 @@ public class CompositeRoleTest {
             manager.generateRealmKeys(realm);
             realmPublicKey = realm.getPublicKey();
             realm.setAccessTokenLifespan(10000);
+            realm.setRefreshTokenLifespan(10000);
             realm.setAccessCodeLifespanUserAction(1000);
             realm.setAccessCodeLifespan(1000);
             realm.setSslNotRequired(true);