keycloak-uncached

Details

diff --git a/core/src/main/java/org/keycloak/AbstractOAuthClient.java b/core/src/main/java/org/keycloak/AbstractOAuthClient.java
index 592ddab..b5975d6 100755
--- a/core/src/main/java/org/keycloak/AbstractOAuthClient.java
+++ b/core/src/main/java/org/keycloak/AbstractOAuthClient.java
@@ -21,12 +21,14 @@ import java.util.concurrent.atomic.AtomicLong;
  * @version $Revision: 1 $
  */
 public class AbstractOAuthClient {
+    public static final String OAUTH_TOKEN_REQUEST_STATE = "OAuth_Token_Request_State";
     protected String clientId;
     protected String password;
     protected KeyStore truststore;
     protected String authUrl;
     protected String codeUrl;
-    protected String stateCookieName = "OAuth_Token_Request_State";
+    protected String stateCookieName = OAUTH_TOKEN_REQUEST_STATE;
+    protected String stateCookiePath;
     protected Client client;
     protected boolean isSecure;
     protected final AtomicLong counter = new AtomicLong();
@@ -35,6 +37,9 @@ public class AbstractOAuthClient {
         return counter.getAndIncrement() + "/" + UUID.randomUUID().toString();
     }
 
+    /**
+     * Creates a Client for obtaining access token from code
+     */
     public void start() {
         if (client == null) {
             client = new ResteasyClientBuilder().trustStore(truststore)
@@ -44,6 +49,9 @@ public class AbstractOAuthClient {
         }
     }
 
+    /**
+     * closes cllient
+     */
     public void stop() {
         client.close();
     }
@@ -76,6 +84,8 @@ public class AbstractOAuthClient {
         return authUrl;
     }
 
+
+
     public void setAuthUrl(String authUrl) {
         this.authUrl = authUrl;
     }
@@ -96,6 +106,14 @@ public class AbstractOAuthClient {
         this.stateCookieName = stateCookieName;
     }
 
+    public String getStateCookiePath() {
+        return stateCookiePath;
+    }
+
+    public void setStateCookiePath(String stateCookiePath) {
+        this.stateCookiePath = stateCookiePath;
+    }
+
     public Client getClient() {
         return client;
     }
@@ -128,7 +146,6 @@ public class AbstractOAuthClient {
     }
 
     protected String stripOauthParametersFromRedirect(String uri) {
-        System.out.println("******************** redirect_uri: " + uri);
         UriBuilder builder = UriBuilder.fromUri(uri)
                 .replaceQueryParam("code", null)
                 .replaceQueryParam("state", null);
diff --git a/core/src/main/java/org/keycloak/jaxrs/JaxrsOAuthClient.java b/core/src/main/java/org/keycloak/jaxrs/JaxrsOAuthClient.java
index 380d3ae..71b58d7 100755
--- a/core/src/main/java/org/keycloak/jaxrs/JaxrsOAuthClient.java
+++ b/core/src/main/java/org/keycloak/jaxrs/JaxrsOAuthClient.java
@@ -1,5 +1,6 @@
 package org.keycloak.jaxrs;
 
+import org.jboss.resteasy.logging.Logger;
 import org.keycloak.AbstractOAuthClient;
 
 import javax.ws.rs.BadRequestException;
@@ -19,6 +20,7 @@ import java.net.URI;
  * @version $Revision: 1 $
  */
 public class JaxrsOAuthClient extends AbstractOAuthClient {
+    protected static final Logger logger = Logger.getLogger(JaxrsOAuthClient.class);
     public Response redirect(UriInfo uriInfo, String redirectUri) {
         String state = getStateCode();
 
@@ -27,26 +29,42 @@ public class JaxrsOAuthClient extends AbstractOAuthClient {
                 .queryParam("redirect_uri", redirectUri)
                 .queryParam("state", state)
                 .build();
-        NewCookie cookie = new NewCookie(stateCookieName, state, uriInfo.getBaseUri().getPath(), null, null, -1, true);
+        NewCookie cookie = new NewCookie(getStateCookieName(), state, getStateCookiePath(uriInfo), null, null, -1, isSecure, true);
+        logger.info("NewCookie: " + cookie.toString());
         return Response.status(302)
                 .location(url)
                 .cookie(cookie).build();
     }
 
+    public String getStateCookiePath(UriInfo uriInfo) {
+        if (stateCookiePath != null) return stateCookiePath;
+        return uriInfo.getBaseUri().getPath();
+    }
+
     public String getBearerToken(UriInfo uriInfo, HttpHeaders headers) throws BadRequestException, InternalServerErrorException {
-        String error = uriInfo.getQueryParameters().getFirst("error");
+        String error = getError(uriInfo);
         if (error != null) throw new BadRequestException(new Exception("OAuth error: " + error));
-        Cookie stateCookie = headers.getCookies().get(stateCookieName);
-        if (stateCookie == null) throw new BadRequestException(new Exception("state cookie not set"));
-        ;
+        checkStateCookie(uriInfo, headers);
+        String code = getAccessCode(uriInfo);
+        if (code == null) throw new BadRequestException(new Exception("code parameter was null"));
+        return resolveBearerToken(uriInfo.getRequestUri().toString(), code);
+    }
 
+    public String getError(UriInfo uriInfo) {
+        return uriInfo.getQueryParameters().getFirst("error");
+    }
+
+    public String getAccessCode(UriInfo uriInfo) {
+        return uriInfo.getQueryParameters().getFirst("code");
+    }
+
+    public void checkStateCookie(UriInfo uriInfo, HttpHeaders headers) {
+        Cookie stateCookie = headers.getCookies().get(stateCookieName);
+        if (stateCookie == null) throw new BadRequestException("state cookie not set");
         String state = uriInfo.getQueryParameters().getFirst("state");
-        if (state == null) throw new BadRequestException(new Exception("state parameter was null"));
+        if (state == null) throw new BadRequestException("state parameter was null");
         if (!state.equals(stateCookie.getValue())) {
-            throw new BadRequestException(new Exception("state parameter invalid"));
+            throw new BadRequestException("state parameter invalid");
         }
-        String code = uriInfo.getQueryParameters().getFirst("code");
-        if (code == null) throw new BadRequestException(new Exception("code parameter was null"));
-        return resolveBearerToken(uriInfo.getRequestUri().toString(), code);
     }
 }
diff --git a/core/src/main/java/org/keycloak/servlet/ServletOAuthClient.java b/core/src/main/java/org/keycloak/servlet/ServletOAuthClient.java
index 27693b4..95ea522 100755
--- a/core/src/main/java/org/keycloak/servlet/ServletOAuthClient.java
+++ b/core/src/main/java/org/keycloak/servlet/ServletOAuthClient.java
@@ -51,12 +51,13 @@ public class ServletOAuthClient extends AbstractOAuthClient {
                 .queryParam("redirect_uri", redirectUri)
                 .queryParam("state", state)
                 .build();
-        String cookiePath = request.getContextPath();
-        if (cookiePath.equals("")) cookiePath = "/";
+        String stateCookiePath = this.stateCookiePath;
+        if (stateCookiePath == null) stateCookiePath = request.getContextPath();
+        if (stateCookiePath.equals("")) stateCookiePath = "/";
 
         Cookie cookie = new Cookie(stateCookieName, state);
         cookie.setSecure(isSecure);
-        cookie.setPath(cookiePath);
+        cookie.setPath(stateCookiePath);
         response.addCookie(cookie);
         response.sendRedirect(url.toString());
     }
diff --git a/forms/src/main/java/org/keycloak/forms/UrlBean.java b/forms/src/main/java/org/keycloak/forms/UrlBean.java
old mode 100644
new mode 100755
index 0fb83c0..f527d38
--- a/forms/src/main/java/org/keycloak/forms/UrlBean.java
+++ b/forms/src/main/java/org/keycloak/forms/UrlBean.java
@@ -70,19 +70,11 @@ public class UrlBean {
     }
 
     public String getLoginAction() {
-        if (realm.isSaas()) {
-            return Urls.saasLoginAction(baseURI).toString();
-        } else {
-            return Urls.realmLoginAction(baseURI, realm.getId()).toString();
-        }
+        return Urls.realmLoginAction(baseURI, realm.getId()).toString();
     }
 
     public String getLoginUrl() {
-        if (realm.isSaas()) {
-            return Urls.saasLoginPage(baseURI).toString();
-        } else {
-            return Urls.realmLoginPage(baseURI, realm.getId()).toString();
-        }
+        return Urls.realmLoginPage(baseURI, realm.getId()).toString();
     }
 
     public String getPasswordUrl() {
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 fd1189f..9dceed8 100755
--- a/services/src/main/java/org/keycloak/services/managers/AccessCodeEntry.java
+++ b/services/src/main/java/org/keycloak/services/managers/AccessCodeEntry.java
@@ -1,5 +1,6 @@
 package org.keycloak.services.managers;
 
+import org.keycloak.models.RealmModel;
 import org.keycloak.representations.SkeletonKeyToken;
 import org.keycloak.models.RoleModel;
 import org.keycloak.models.UserModel;
@@ -23,6 +24,7 @@ public class AccessCodeEntry {
     protected String redirectUri;
 
     protected long expiration;
+    protected RealmModel realm;
     protected SkeletonKeyToken token;
     protected UserModel user;
     protected Set<RequiredAction> requiredActions;
@@ -38,6 +40,14 @@ public class AccessCodeEntry {
         return id;
     }
 
+    public RealmModel getRealm() {
+        return realm;
+    }
+
+    public void setRealm(RealmModel realm) {
+        this.realm = realm;
+    }
+
     public String getCode() {
         return code;
     }
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 e6d3cbb..9d36f8f 100755
--- a/services/src/main/java/org/keycloak/services/managers/ApplianceBootstrap.java
+++ b/services/src/main/java/org/keycloak/services/managers/ApplianceBootstrap.java
@@ -26,6 +26,7 @@ public class ApplianceBootstrap {
         realm.addRequiredResourceCredential(CredentialRepresentation.PASSWORD);
         realm.setTokenLifespan(300);
         realm.setAccessCodeLifespan(60);
+        realm.setAccessCodeLifespanUserAction(300);
         realm.setSslNotRequired(true);
         realm.setCookieLoginAllowed(true);
         realm.setRegistrationAllowed(false);
@@ -49,7 +50,7 @@ public class ApplianceBootstrap {
         password.setType(UserCredentialModel.PASSWORD);
         password.setValue("admin");
         realm.updateCredential(adminUser, password);
-        //adminUser.addRequiredAction(UserModel.RequiredAction.UPDATE_PASSWORD);
+        adminUser.addRequiredAction(UserModel.RequiredAction.UPDATE_PASSWORD);
 
         adminConsole.grantRole(adminUser, adminRole);
 
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 b48d710..925076a 100755
--- a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
@@ -99,7 +99,7 @@ public class AuthenticationManager {
         expireCookie(SaasService.SAAS_IDENTITY_COOKIE, cookiePath);
     }
 
-    protected void expireCookie(String cookieName, String path) {
+    public void expireCookie(String cookieName, String path) {
         HttpResponse response = ResteasyProviderFactory.getContextData(HttpResponse.class);
         if (response == null) {
             logger.info("can't expire identity cookie, no HttpResponse");
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 47ed94d..e3012b6 100755
--- a/services/src/main/java/org/keycloak/services/managers/TokenManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/TokenManager.java
@@ -3,6 +3,7 @@ package org.keycloak.services.managers;
 import org.jboss.resteasy.jose.Base64Url;
 import org.jboss.resteasy.jose.jws.JWSBuilder;
 import org.jboss.resteasy.jwt.JsonSerialization;
+import org.jboss.resteasy.logging.Logger;
 import org.keycloak.models.*;
 import org.keycloak.representations.SkeletonKeyScope;
 import org.keycloak.representations.SkeletonKeyToken;
@@ -22,6 +23,7 @@ import java.util.concurrent.ConcurrentHashMap;
  * @version $Revision: 1 $
  */
 public class TokenManager {
+    protected static final Logger logger = Logger.getLogger(TokenManager.class);
 
     protected Map<String, AccessCodeEntry> accessCodeMap = new ConcurrentHashMap<String, AccessCodeEntry>();
 
@@ -86,6 +88,9 @@ public class TokenManager {
 
 
         createToken(code, realm, client, user);
+        logger.info("tokenmanager: access code id: " + code.getId());
+        logger.info("accesscode setExpiration: " + (System.currentTimeMillis() / 1000) + realm.getAccessCodeLifespan());
+        code.setRealm(realm);
         code.setExpiration((System.currentTimeMillis() / 1000) + realm.getAccessCodeLifespan());
         code.setClient(client);
         code.setUser(user);
diff --git a/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java b/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java
index 0340751..7095eab 100755
--- a/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java
+++ b/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java
@@ -47,10 +47,9 @@ public class KeycloakApplication extends Application {
         TokenManager tokenManager = new TokenManager();
 
         singletons.add(new RealmsResource(tokenManager));
+        singletons.add(new SaasService(tokenManager));
         singletons.add(new SocialResource(tokenManager, new SocialRequestManager()));
         classes.add(SkeletonKeyContextResolver.class);
-        classes.add(SaasService.class);
-
     }
 
     protected KeycloakSessionFactory createSessionFactory() {
diff --git a/services/src/main/java/org/keycloak/services/resources/RequiredActionsService.java b/services/src/main/java/org/keycloak/services/resources/RequiredActionsService.java
index 214a814..1517b79 100755
--- a/services/src/main/java/org/keycloak/services/resources/RequiredActionsService.java
+++ b/services/src/main/java/org/keycloak/services/resources/RequiredActionsService.java
@@ -23,6 +23,7 @@ package org.keycloak.services.resources;
 
 import org.jboss.resteasy.jose.jws.JWSInput;
 import org.jboss.resteasy.jose.jws.crypto.RSAProvider;
+import org.jboss.resteasy.logging.Logger;
 import org.jboss.resteasy.spi.HttpRequest;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.UserCredentialModel;
@@ -52,6 +53,7 @@ import java.util.Set;
  * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
  */
 public class RequiredActionsService {
+    protected static final Logger logger = Logger.getLogger(RequiredActionsService.class);
 
     private RealmModel realm;
 
@@ -134,10 +136,13 @@ public class RequiredActionsService {
     @POST
     @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
     public Response updatePassword(final MultivaluedMap<String, String> formData) {
+        logger.info("updatePassword");
         AccessCodeEntry accessCode = getAccessCodeEntry(RequiredAction.UPDATE_PASSWORD);
         if (accessCode == null) {
+            logger.info("updatePassword access code is null");
             return forwardToErrorPage();
         }
+        logger.info("updatePassword has access code");
 
         UserModel user = getUser(accessCode);
 
@@ -158,6 +163,8 @@ public class RequiredActionsService {
 
         realm.updateCredential(user, credentials);
 
+        logger.info("updatePassword updated credential");
+
         user.removeRequiredAction(RequiredAction.UPDATE_PASSWORD);
         if (accessCode != null) {
             accessCode.getRequiredActions().remove(RequiredAction.UPDATE_PASSWORD);
@@ -257,6 +264,7 @@ public class RequiredActionsService {
     private AccessCodeEntry getAccessCodeEntry(RequiredAction requiredAction) {
         String code = uriInfo.getQueryParameters().getFirst(FormFlows.CODE);
         if (code == null) {
+            logger.info("getAccessCodeEntry code as not in query param");
             return null;
         }
 
@@ -265,24 +273,31 @@ public class RequiredActionsService {
         try {
             verifiedCode = RSAProvider.verify(input, realm.getPublicKey());
         } catch (Exception ignored) {
+            logger.info("getAccessCodeEntry code failed verification");
             return null;
         }
 
         if (!verifiedCode) {
+            logger.info("getAccessCodeEntry code failed verification2");
             return null;
         }
 
         String key = input.readContent(String.class);
         AccessCodeEntry accessCodeEntry = tokenManager.getAccessCode(key);
         if (accessCodeEntry == null) {
+            logger.info("getAccessCodeEntry access code entry null");
             return null;
         }
 
         if (accessCodeEntry.isExpired()) {
+            logger.info("getAccessCodeEntry: access code id: " + accessCodeEntry.getId());
+            logger.info("getAccessCodeEntry access code entry expired: " + accessCodeEntry.getExpiration());
+            logger.info("getAccessCodeEntry current time: " + (System.currentTimeMillis() / 1000));
             return null;
         }
 
         if (accessCodeEntry.getRequiredActions() == null || !accessCodeEntry.getRequiredActions().contains(requiredAction)) {
+            logger.info("getAccessCodeEntry required actions null || entry does not contain required action: " + (accessCodeEntry.getRequiredActions() == null) + "|" + !accessCodeEntry.getRequiredActions().contains(requiredAction) );
             return null;
         }
 
@@ -303,6 +318,7 @@ public class RequiredActionsService {
             return Flows.forms(realm, request, uriInfo).setAccessCode(accessCode).setUser(user)
                     .forwardToAction(requiredActions.iterator().next());
         } else {
+            logger.info("redirectOauth: redirecting to: " + accessCode.getRedirectUri());
             accessCode.setExpiration((System.currentTimeMillis() / 1000) + realm.getAccessCodeLifespan());
             return Flows.oauth(realm, request, uriInfo, authManager, tokenManager).redirectAccessCode(accessCode,
                     accessCode.getState(), accessCode.getRedirectUri());
diff --git a/services/src/main/java/org/keycloak/services/resources/SaasService.java b/services/src/main/java/org/keycloak/services/resources/SaasService.java
index 0b2c08d..b8d2e82 100755
--- a/services/src/main/java/org/keycloak/services/resources/SaasService.java
+++ b/services/src/main/java/org/keycloak/services/resources/SaasService.java
@@ -1,21 +1,33 @@
 package org.keycloak.services.resources;
 
 import org.jboss.resteasy.annotations.cache.NoCache;
+import org.jboss.resteasy.jose.jws.JWSInput;
+import org.jboss.resteasy.jose.jws.crypto.RSAProvider;
 import org.jboss.resteasy.logging.Logger;
 import org.jboss.resteasy.spi.HttpRequest;
 import org.jboss.resteasy.spi.HttpResponse;
 import org.jboss.resteasy.spi.NotImplementedYetException;
+import org.keycloak.AbstractOAuthClient;
+import org.keycloak.jaxrs.JaxrsOAuthClient;
 import org.keycloak.models.*;
+import org.keycloak.representations.AccessTokenResponse;
+import org.keycloak.services.managers.AccessCodeEntry;
 import org.keycloak.services.managers.AuthenticationManager;
 import org.keycloak.services.managers.AuthenticationManager.AuthenticationStatus;
 import org.keycloak.services.managers.RealmManager;
+import org.keycloak.services.managers.TokenManager;
 import org.keycloak.services.messages.Messages;
 import org.keycloak.services.resources.admin.RealmsAdminResource;
 import org.keycloak.services.resources.flows.Flows;
+import org.keycloak.services.resources.flows.OAuthFlows;
 
 import javax.ws.rs.*;
 import javax.ws.rs.container.ResourceContext;
 import javax.ws.rs.core.*;
+import javax.ws.rs.ext.Providers;
+import java.net.URI;
+import java.util.HashMap;
+import java.util.Map;
 
 /**
  * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@@ -42,8 +54,17 @@ public class SaasService {
     @Context
     protected ResourceContext resourceContext;
 
+    @Context
+    protected Providers providers;
+
+
     protected String adminPath = "/admin/index.html";
     protected AuthenticationManager authManager = new AuthenticationManager();
+    protected TokenManager tokenManager;
+
+    public SaasService(TokenManager tokenManager) {
+        this.tokenManager = tokenManager;
+    }
 
     public static class WhoAmI {
         protected String userId;
@@ -168,7 +189,99 @@ public class SaasService {
         RealmModel realm = getAdminstrationRealm(realmManager);
         authManager.expireSaasIdentityCookie(uriInfo);
 
-        return Flows.forms(realm, request, uriInfo).forwardToLogin();
+        JaxrsOAuthClient oauth = new JaxrsOAuthClient();
+        String authUrl = TokenService.loginPageUrl(uriInfo).build(Constants.ADMIN_REALM).toString();
+        logger.info("authUrl: " + authUrl);
+        oauth.setAuthUrl(authUrl);
+        oauth.setClientId(Constants.ADMIN_CONSOLE_APPLICATION);
+        URI redirectUri = uriInfo.getBaseUriBuilder().path(SaasService.class).path(SaasService.class, "loginRedirect").build();
+        logger.info("redirectUri: " + redirectUri.toString());
+        oauth.setStateCookiePath(redirectUri.getPath());
+        return oauth.redirect(uriInfo, redirectUri.toString());
+    }
+
+    @Path("login-redirect")
+    @GET
+    @NoCache
+    public Response loginRedirect(@QueryParam("code") String code,
+                                  @QueryParam("state") String state,
+                                  @QueryParam("error") String error,
+                                  @Context HttpHeaders headers
+
+                                  ) {
+        try {
+            logger.info("loginRedirect ********************** <---");
+            if (error != null) {
+                logger.debug("error from oauth");
+                throw new ForbiddenException("error");
+            }
+            RealmManager realmManager = new RealmManager(session);
+            RealmModel realm = getAdminstrationRealm(realmManager);
+            if (!realm.isEnabled()) {
+                logger.debug("realm not enabled");
+                throw new ForbiddenException();
+            }
+            ApplicationModel adminConsole = realm.getApplicationNameMap().get(Constants.ADMIN_CONSOLE_APPLICATION);
+            UserModel adminConsoleUser = adminConsole.getApplicationUser();
+            if (!adminConsole.isEnabled() || !adminConsoleUser.isEnabled()) {
+                logger.debug("admin app not enabled");
+                throw new ForbiddenException();
+            }
+
+            if (code == null) {
+                logger.debug("code not specified");
+                throw new BadRequestException();
+            }
+            if (state == null) {
+                logger.debug("state not specified");
+                throw new BadRequestException();
+            }
+            new JaxrsOAuthClient().checkStateCookie(uriInfo, headers);
+
+            JWSInput input = new JWSInput(code, providers);
+            boolean verifiedCode = false;
+            try {
+                verifiedCode = RSAProvider.verify(input, realm.getPublicKey());
+            } catch (Exception ignored) {
+                logger.debug("Failed to verify signature", ignored);
+            }
+            if (!verifiedCode) {
+                logger.debug("unverified access code");
+                throw new BadRequestException();
+            }
+            String key = input.readContent(String.class);
+            AccessCodeEntry accessCode = tokenManager.pullAccessCode(key);
+            if (accessCode == null) {
+                logger.debug("bad access code");
+                throw new BadRequestException();
+            }
+            if (accessCode.isExpired()) {
+                logger.debug("access code expired");
+                throw new BadRequestException();
+            }
+            if (!accessCode.getToken().isActive()) {
+                logger.debug("access token expired");
+                throw new BadRequestException();
+            }
+            if (!accessCode.getRealm().getId().equals(realm.getId())) {
+                logger.debug("bad realm");
+                throw new BadRequestException();
+
+            }
+            if (!adminConsoleUser.getLoginName().equals(accessCode.getClient().getLoginName())) {
+                logger.debug("bad client");
+                throw new BadRequestException();
+            }
+            if (!adminConsole.hasRole(accessCode.getUser(), Constants.ADMIN_CONSOLE_ADMIN_ROLE)) {
+                logger.debug("not allowed");
+                throw new ForbiddenException();
+            }
+            logger.info("loginRedirect SUCCESS");
+            NewCookie cookie = authManager.createSaasIdentityCookie(realm, accessCode.getUser(), uriInfo);
+            return Response.status(302).cookie(cookie).location(contextRoot(uriInfo).path(adminPath).build()).build();
+        } finally {
+            authManager.expireCookie(AbstractOAuthClient.OAUTH_TOKEN_REQUEST_STATE, uriInfo.getAbsolutePath().getPath());
+        }
     }
 
     @Path("logout")
@@ -178,8 +291,9 @@ public class SaasService {
         RealmManager realmManager = new RealmManager(session);
         RealmModel realm = getAdminstrationRealm(realmManager);
         authManager.expireSaasIdentityCookie(uriInfo);
+        authManager.expireIdentityCookie(realm, uriInfo);
 
-        return Flows.forms(realm, request, uriInfo).forwardToLogin();
+        return Response.status(302).location(uriInfo.getBaseUriBuilder().path(SaasService.class).path(SaasService.class, "loginPage").build()).build();
     }
 
     @Path("logout-cookie")
@@ -199,6 +313,8 @@ public class SaasService {
         RealmModel realm = getAdminstrationRealm(realmManager);
         if (realm == null)
             throw new NotFoundException();
+        ApplicationModel adminConsole = realm.getApplicationNameMap().get(Constants.ADMIN_CONSOLE_APPLICATION);
+        UserModel adminConsoleUser = adminConsole.getApplicationUser();
 
         if (!realm.isEnabled()) {
             throw new NotImplementedYetException();
@@ -208,6 +324,8 @@ public class SaasService {
 
         AuthenticationStatus status = authManager.authenticateForm(realm, user, formData);
 
+        OAuthFlows oauth = Flows.oauth(realm, request, uriInfo, authManager, tokenManager);
+
         switch (status) {
             case SUCCESS:
                 NewCookie cookie = authManager.createSaasIdentityCookie(realm, user, uriInfo);
@@ -216,7 +334,7 @@ public class SaasService {
                 return Flows.forms(realm, request, uriInfo).setError(Messages.ACCOUNT_DISABLED).setFormData(formData)
                         .forwardToLogin();
             case ACTIONS_REQUIRED:
-                return Flows.forms(realm, request, uriInfo).forwardToAction(user.getRequiredActions().iterator().next());
+                return oauth.processAccessCode(null, "n", contextRoot(uriInfo).path(adminPath).build().toString(), adminConsoleUser, user);
             default:
                 return Flows.forms(realm, request, uriInfo).setError(Messages.INVALID_USER).setFormData(formData)
                         .forwardToLogin();