keycloak-aplcache

Changes

services/pom.xml 5(+0 -5)

services/src/main/java/org/keycloak/services/protocol/OpenIdConnectProtocol.java 17(+0 -17)

services/src/main/java/org/keycloak/services/resources/flows/OAuthFlows.java 276(+0 -276)

Details

diff --git a/forms/login-api/src/main/java/org/keycloak/login/LoginFormsProvider.java b/forms/login-api/src/main/java/org/keycloak/login/LoginFormsProvider.java
index 1aede52..3e1eb51 100755
--- a/forms/login-api/src/main/java/org/keycloak/login/LoginFormsProvider.java
+++ b/forms/login-api/src/main/java/org/keycloak/login/LoginFormsProvider.java
@@ -36,7 +36,7 @@ public interface LoginFormsProvider extends Provider {
 
     public Response createCode();
 
-    public LoginFormsProvider setAccessCode(String accessCode);
+    public LoginFormsProvider setClientSessionCode(String accessCode);
 
     public LoginFormsProvider setAccessRequest(List<RoleModel> realmRolesRequested, MultivaluedMap<String,RoleModel> resourceRolesRequested);
 
diff --git a/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/FreeMarkerLoginFormsProvider.java b/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/FreeMarkerLoginFormsProvider.java
index 343c3c1..9a76e78 100755
--- a/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/FreeMarkerLoginFormsProvider.java
+++ b/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/FreeMarkerLoginFormsProvider.java
@@ -293,7 +293,7 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
     }
 
     @Override
-    public LoginFormsProvider setAccessCode(String accessCode) {
+    public LoginFormsProvider setClientSessionCode(String accessCode) {
         this.accessCode = accessCode;
         return this;
     }

services/pom.xml 5(+0 -5)

diff --git a/services/pom.xml b/services/pom.xml
index 0e5e597..e2390b7 100755
--- a/services/pom.xml
+++ b/services/pom.xml
@@ -15,11 +15,6 @@
 
     <dependencies>
         <dependency>
-            <groupId>org.picketlink</groupId>
-            <artifactId>picketlink-federation</artifactId>
-            <version>2.7.0-SNAPSHOT</version>
-        </dependency>
-        <dependency>
             <groupId>org.bouncycastle</groupId>
             <artifactId>bcprov-jdk16</artifactId>
             <scope>provided</scope>
diff --git a/services/src/main/java/org/keycloak/protocol/LoginProtocol.java b/services/src/main/java/org/keycloak/protocol/LoginProtocol.java
new file mode 100755
index 0000000..6790adc
--- /dev/null
+++ b/services/src/main/java/org/keycloak/protocol/LoginProtocol.java
@@ -0,0 +1,37 @@
+package org.keycloak.protocol;
+
+import org.jboss.resteasy.spi.HttpRequest;
+import org.keycloak.ClientConnection;
+import org.keycloak.events.EventBuilder;
+import org.keycloak.models.ClientSessionModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserSessionModel;
+import org.keycloak.provider.Provider;
+import org.keycloak.services.managers.AuthenticationManager;
+import org.keycloak.protocol.oidc.OAuthFlows;
+import org.keycloak.services.managers.ClientSessionCode;
+
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriInfo;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public interface LoginProtocol extends Provider {
+    OAuthFlows setSession(KeycloakSession session);
+
+    OAuthFlows setRealm(RealmModel realm);
+
+    OAuthFlows setRequest(HttpRequest request);
+
+    OAuthFlows setUriInfo(UriInfo uriInfo);
+
+    OAuthFlows setClientConnection(ClientConnection clientConnection);
+
+    Response cancelLogin(ClientSessionModel clientSession);
+    Response invalidSessionError(ClientSessionModel clientSession);
+    Response authenticated(UserSessionModel userSession, ClientSessionCode accessCode);
+    Response consentDenied(ClientSessionModel clientSession);
+}
diff --git a/services/src/main/java/org/keycloak/protocol/LoginProtocolFactory.java b/services/src/main/java/org/keycloak/protocol/LoginProtocolFactory.java
new file mode 100755
index 0000000..817b224
--- /dev/null
+++ b/services/src/main/java/org/keycloak/protocol/LoginProtocolFactory.java
@@ -0,0 +1,10 @@
+package org.keycloak.protocol;
+
+import org.keycloak.provider.ProviderFactory;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public interface LoginProtocolFactory extends ProviderFactory<LoginProtocol> {
+}
diff --git a/services/src/main/java/org/keycloak/protocol/LoginProtocolSpi.java b/services/src/main/java/org/keycloak/protocol/LoginProtocolSpi.java
new file mode 100755
index 0000000..ef73b34
--- /dev/null
+++ b/services/src/main/java/org/keycloak/protocol/LoginProtocolSpi.java
@@ -0,0 +1,27 @@
+package org.keycloak.protocol;
+
+import org.keycloak.provider.Provider;
+import org.keycloak.provider.ProviderFactory;
+import org.keycloak.provider.Spi;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class LoginProtocolSpi implements Spi {
+
+    @Override
+    public String getName() {
+        return "login-protocol";
+    }
+
+    @Override
+    public Class<? extends Provider> getProviderClass() {
+        return LoginProtocol.class;
+    }
+
+    @Override
+    public Class<? extends ProviderFactory> getProviderFactoryClass() {
+        return LoginProtocolFactory.class;
+    }
+
+}
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/OAuthFlows.java b/services/src/main/java/org/keycloak/protocol/oidc/OAuthFlows.java
new file mode 100755
index 0000000..1496070
--- /dev/null
+++ b/services/src/main/java/org/keycloak/protocol/oidc/OAuthFlows.java
@@ -0,0 +1,178 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2012, Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.keycloak.protocol.oidc;
+
+import org.jboss.logging.Logger;
+import org.jboss.resteasy.specimpl.MultivaluedMapImpl;
+import org.jboss.resteasy.spi.HttpRequest;
+import org.keycloak.ClientConnection;
+import org.keycloak.OAuth2Constants;
+import org.keycloak.events.EventBuilder;
+import org.keycloak.events.Details;
+import org.keycloak.events.EventType;
+import org.keycloak.login.LoginFormsProvider;
+import org.keycloak.models.ApplicationModel;
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.ClientSessionModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.RequiredCredentialModel;
+import org.keycloak.models.RoleModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.models.UserModel.RequiredAction;
+import org.keycloak.models.UserSessionModel;
+import org.keycloak.representations.idm.CredentialRepresentation;
+import org.keycloak.services.managers.AuthenticationManager;
+import org.keycloak.services.managers.ClientSessionCode;
+import org.keycloak.protocol.LoginProtocol;
+import org.keycloak.services.resources.flows.Flows;
+
+import javax.ws.rs.core.Cookie;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriBuilder;
+import javax.ws.rs.core.UriInfo;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+import java.util.UUID;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class OAuthFlows implements LoginProtocol {
+
+    public static final String LOGIN_PAGE_PROTOCOL = "openid-connect";
+    public static final String STATE_PARAM = "state";
+    public static final String SCOPE_PARAM = "scope";
+    public static final String RESPONSE_TYPE_PARAM = "response_type";
+    public static final String REDIRECT_URI_PARAM = "redirect_uri";
+    public static final String CLIENT_ID_PARAM = "client_id";
+    public static final String PROMPT_PARAM = "prompt";
+    public static final String LOGIN_HINT_PARAM = "login_hint";
+    private static final Logger log = Logger.getLogger(OAuthFlows.class);
+
+    protected KeycloakSession session;
+
+    protected RealmModel realm;
+
+    protected HttpRequest request;
+
+    protected UriInfo uriInfo;
+
+    protected ClientConnection clientConnection;
+
+    public OAuthFlows(KeycloakSession session, RealmModel realm, HttpRequest request, UriInfo uriInfo,
+               ClientConnection clientConnection) {
+        this.session = session;
+        this.realm = realm;
+        this.request = request;
+        this.uriInfo = uriInfo;
+        this.clientConnection = clientConnection;
+    }
+
+    public OAuthFlows() {
+    }
+
+    @Override
+    public OAuthFlows setSession(KeycloakSession session) {
+        this.session = session;
+        return this;
+    }
+
+    @Override
+    public OAuthFlows setRealm(RealmModel realm) {
+        this.realm = realm;
+        return this;
+    }
+
+    @Override
+    public OAuthFlows setRequest(HttpRequest request) {
+        this.request = request;
+        return this;
+    }
+
+    @Override
+    public OAuthFlows setUriInfo(UriInfo uriInfo) {
+        this.uriInfo = uriInfo;
+        return this;
+    }
+
+    @Override
+    public OAuthFlows setClientConnection(ClientConnection clientConnection) {
+        this.clientConnection = clientConnection;
+        return this;
+    }
+
+    @Override
+    public Response cancelLogin(ClientSessionModel clientSession) {
+        String redirect = clientSession.getRedirectUri();
+        String state = clientSession.getNote(OAuthFlows.STATE_PARAM);
+        UriBuilder redirectUri = UriBuilder.fromUri(redirect).queryParam(OAuth2Constants.ERROR, "access_denied");
+        if (state != null) {
+            redirectUri.queryParam(OAuth2Constants.STATE, state);
+        }
+        return Response.status(302).location(redirectUri.build()).build();
+    }
+
+    @Override
+    public Response authenticated(UserSessionModel userSession, ClientSessionCode accessCode) {
+        ClientSessionModel clientSession = accessCode.getClientSession();
+        String redirect = clientSession.getRedirectUri();
+        String state = clientSession.getNote(OAuthFlows.STATE_PARAM);
+        accessCode.setAction(ClientSessionModel.Action.CODE_TO_TOKEN);
+        UriBuilder redirectUri = UriBuilder.fromUri(redirect).queryParam(OAuth2Constants.CODE, accessCode.getCode());
+        log.debugv("redirectAccessCode: state: {0}", state);
+        if (state != null)
+            redirectUri.queryParam(OAuth2Constants.STATE, state);
+        Response.ResponseBuilder location = Response.status(302).location(redirectUri.build());
+
+        return location.build();
+    }
+
+    public Response consentDenied(ClientSessionModel clientSession) {
+        String redirect = clientSession.getRedirectUri();
+        String state = clientSession.getNote(OAuthFlows.STATE_PARAM);
+        UriBuilder redirectUri = UriBuilder.fromUri(redirect).queryParam(OAuth2Constants.ERROR, "access_denied");
+        if (state != null)
+            redirectUri.queryParam(OAuth2Constants.STATE, state);
+        Response.ResponseBuilder location = Response.status(302).location(redirectUri.build());
+        return location.build();
+    }
+
+
+    public Response invalidSessionError(ClientSessionModel clientSession) {
+        String redirect = clientSession.getRedirectUri();
+        String state = clientSession.getNote(OAuthFlows.STATE_PARAM);
+        UriBuilder redirectUri = UriBuilder.fromUri(redirect).queryParam(OAuth2Constants.ERROR, "access_denied");
+        if (state != null) {
+            redirectUri.queryParam(OAuth2Constants.STATE, state);
+        }
+        return Response.status(302).location(redirectUri.build()).build();
+    }
+
+    @Override
+    public void close() {
+
+    }
+}
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/OpenIDConnectFactory.java b/services/src/main/java/org/keycloak/protocol/oidc/OpenIDConnectFactory.java
new file mode 100755
index 0000000..5c35daf
--- /dev/null
+++ b/services/src/main/java/org/keycloak/protocol/oidc/OpenIDConnectFactory.java
@@ -0,0 +1,32 @@
+package org.keycloak.protocol.oidc;
+
+import org.keycloak.Config;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.protocol.LoginProtocol;
+import org.keycloak.protocol.LoginProtocolFactory;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class OpenIDConnectFactory implements LoginProtocolFactory {
+    @Override
+    public LoginProtocol create(KeycloakSession session) {
+        return new OAuthFlows().setSession(session);
+    }
+
+    @Override
+    public void init(Config.Scope config) {
+
+    }
+
+    @Override
+    public void close() {
+
+    }
+
+    @Override
+    public String getId() {
+        return "openid-connect";
+    }
+}
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 ae0b20c..f64fc0a 100755
--- a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
@@ -1,20 +1,32 @@
 package org.keycloak.services.managers;
 
 import org.jboss.logging.Logger;
+import org.jboss.resteasy.specimpl.MultivaluedMapImpl;
+import org.jboss.resteasy.spi.HttpRequest;
 import org.keycloak.ClientConnection;
 import org.keycloak.RSATokenVerifier;
 import org.keycloak.VerificationException;
+import org.keycloak.events.Details;
+import org.keycloak.events.EventBuilder;
+import org.keycloak.events.EventType;
 import org.keycloak.jose.jws.JWSBuilder;
+import org.keycloak.login.LoginFormsProvider;
+import org.keycloak.models.ApplicationModel;
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.ClientSessionModel;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.RequiredCredentialModel;
+import org.keycloak.models.RoleModel;
 import org.keycloak.models.UserCredentialModel;
 import org.keycloak.models.UserModel;
 import org.keycloak.models.UserSessionModel;
 import org.keycloak.models.utils.KeycloakModelUtils;
+import org.keycloak.protocol.LoginProtocol;
 import org.keycloak.representations.AccessToken;
 import org.keycloak.representations.idm.CredentialRepresentation;
 import org.keycloak.services.resources.RealmsResource;
+import org.keycloak.services.resources.flows.Flows;
 import org.keycloak.services.util.CookieHelper;
 import org.keycloak.util.Time;
 
@@ -22,12 +34,14 @@ import javax.ws.rs.core.Cookie;
 import javax.ws.rs.core.HttpHeaders;
 import javax.ws.rs.core.MultivaluedMap;
 import javax.ws.rs.core.NewCookie;
+import javax.ws.rs.core.Response;
 import javax.ws.rs.core.UriInfo;
 import java.net.URI;
 import java.util.HashSet;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Set;
+import java.util.UUID;
 
 /**
  * Stateless object that manages authentication
@@ -78,7 +92,7 @@ public class AuthenticationManager {
     }
 
 
-    public AccessToken createIdentityToken(RealmModel realm, UserModel user, UserSessionModel session) {
+    public static AccessToken createIdentityToken(RealmModel realm, UserModel user, UserSessionModel session) {
         AccessToken token = new AccessToken();
         token.id(KeycloakModelUtils.generateId());
         token.issuedNow();
@@ -93,7 +107,7 @@ public class AuthenticationManager {
         return token;
     }
 
-    public void createLoginCookie(RealmModel realm, UserModel user, UserSessionModel session, UriInfo uriInfo, ClientConnection connection) {
+    public static void createLoginCookie(RealmModel realm, UserModel user, UserSessionModel session, UriInfo uriInfo, ClientConnection connection) {
         String cookiePath = getIdentityCookiePath(realm, uriInfo);
         AccessToken identityToken = createIdentityToken(realm, user, session);
         String encoded = encodeToken(realm, identityToken);
@@ -116,7 +130,7 @@ public class AuthenticationManager {
 
     }
 
-    public void createRememberMeCookie(RealmModel realm, String username, UriInfo uriInfo, ClientConnection connection) {
+    public static void createRememberMeCookie(RealmModel realm, String username, UriInfo uriInfo, ClientConnection connection) {
         String path = getIdentityCookiePath(realm, uriInfo);
         boolean secureOnly = realm.getSslRequired().isRequired(connection);
         // remember me cookie should be persistent (hardcoded to 365 days for now)
@@ -124,7 +138,7 @@ public class AuthenticationManager {
         CookieHelper.addCookie(KEYCLOAK_REMEMBER_ME, username, path, null, null, 31536000, secureOnly, true);
     }
 
-    protected String encodeToken(RealmModel realm, Object token) {
+    protected static String encodeToken(RealmModel realm, Object token) {
         String encodedToken = new JWSBuilder()
                 .jsonContent(token)
                 .rsa256(realm.getPrivateKey());
@@ -180,6 +194,114 @@ public class AuthenticationManager {
         return authResult;
     }
 
+    public static Response redirectAfterSuccessfulFlow(KeycloakSession session, RealmModel realm, UserSessionModel userSession,
+                                                ClientSessionModel clientSession,
+                                                HttpRequest request, UriInfo uriInfo, ClientConnection clientConnection) {
+        Cookie sessionCookie = request.getHttpHeaders().getCookies().get(AuthenticationManager.KEYCLOAK_SESSION_COOKIE);
+        if (sessionCookie != null) {
+
+            String[] split = sessionCookie.getValue().split("/");
+            if (split.length >= 3) {
+                String oldSessionId = split[2];
+                if (!oldSessionId.equals(userSession.getId())) {
+                    UserSessionModel oldSession = session.sessions().getUserSession(realm, oldSessionId);
+                    if (oldSession != null) {
+                        logger.debugv("Removing old user session: session: {0}", oldSessionId);
+                        session.sessions().removeUserSession(realm, oldSession);
+                    }
+                }
+            }
+        }
+
+        // refresh the cookies!
+        createLoginCookie(realm, userSession.getUser(), userSession, uriInfo, clientConnection);
+        if (userSession.isRememberMe()) createRememberMeCookie(realm, userSession.getUser().getUsername(), uriInfo, clientConnection);
+        LoginProtocol protocol = session.getProvider(LoginProtocol.class, clientSession.getAuthMethod());
+        protocol.setRealm(realm)
+                .setRequest(request)
+                .setUriInfo(uriInfo)
+                .setClientConnection(clientConnection);
+        return protocol.authenticated(userSession, new ClientSessionCode(realm, clientSession));
+
+    }
+
+    public static Response nextActionAfterAuthentication(KeycloakSession session, UserSessionModel userSession, ClientSessionModel clientSession,
+                                                  ClientConnection clientConnection,
+                                                  HttpRequest request, UriInfo uriInfo, EventBuilder event) {
+        RealmModel realm = clientSession.getRealm();
+        UserModel user = userSession.getUser();
+        isTotpConfigurationRequired(realm, user);
+        isEmailVerificationRequired(realm, user);
+        ClientModel client = clientSession.getClient();
+
+        boolean isResource = client instanceof ApplicationModel;
+        ClientSessionCode accessCode = new ClientSessionCode(realm, clientSession);
+
+
+        logger.debugv("processAccessCode: isResource: {0}", isResource);
+        logger.debugv("processAccessCode: go to oauth page?: {0}",
+                !isResource);
+
+        event.detail(Details.CODE_ID, clientSession.getId());
+
+        Set<UserModel.RequiredAction> requiredActions = user.getRequiredActions();
+        if (!requiredActions.isEmpty()) {
+            UserModel.RequiredAction action = user.getRequiredActions().iterator().next();
+            accessCode.setRequiredAction(action);
+
+            LoginFormsProvider loginFormsProvider = Flows.forms(session, realm, client, uriInfo).setClientSessionCode(accessCode.getCode()).setUser(user);
+            if (action.equals(UserModel.RequiredAction.VERIFY_EMAIL)) {
+                String key = UUID.randomUUID().toString();
+                clientSession.setNote("key", key);
+                loginFormsProvider.setVerifyCode(key);
+                event.clone().event(EventType.SEND_VERIFY_EMAIL).detail(Details.EMAIL, user.getEmail()).success();
+            }
+
+            return loginFormsProvider
+                    .createResponse(action);
+        }
+
+        if (!isResource) {
+            accessCode.setAction(ClientSessionModel.Action.OAUTH_GRANT);
+
+            List<RoleModel> realmRoles = new LinkedList<RoleModel>();
+            MultivaluedMap<String, RoleModel> resourceRoles = new MultivaluedMapImpl<String, RoleModel>();
+            for (RoleModel r : accessCode.getRequestedRoles()) {
+                if (r.getContainer() instanceof RealmModel) {
+                    realmRoles.add(r);
+                } else {
+                    resourceRoles.add(((ApplicationModel) r.getContainer()).getName(), r);
+                }
+            }
+
+            return Flows.forms(session, realm, client, uriInfo)
+                    .setClientSessionCode(accessCode.getCode())
+                    .setAccessRequest(realmRoles, resourceRoles)
+                    .setClient(client)
+                    .createOAuthGrant();
+        }
+
+        event.success();
+        return redirectAfterSuccessfulFlow(session, realm , userSession, clientSession, request, uriInfo, clientConnection);
+
+    }
+
+    protected static void isTotpConfigurationRequired(RealmModel realm, UserModel user) {
+        for (RequiredCredentialModel c : realm.getRequiredCredentials()) {
+            if (c.getType().equals(CredentialRepresentation.TOTP) && !user.isTotp()) {
+                user.addRequiredAction(UserModel.RequiredAction.CONFIGURE_TOTP);
+                logger.debug("User is required to configure totp");
+            }
+        }
+    }
+
+    protected static void isEmailVerificationRequired(RealmModel realm, UserModel user) {
+        if (realm.isVerifyEmail() && !user.isEmailVerified()) {
+            user.addRequiredAction(UserModel.RequiredAction.VERIFY_EMAIL);
+            logger.debug("User is required to verify email");
+        }
+    }
+
     protected AuthResult verifyIdentityToken(KeycloakSession session, RealmModel realm, UriInfo uriInfo, ClientConnection connection, boolean checkActive, String tokenString) {
         try {
             AccessToken token = RSATokenVerifier.verifyToken(tokenString, realm.getPublicKey(), realm.getName(), checkActive);
diff --git a/services/src/main/java/org/keycloak/services/resources/AccountService.java b/services/src/main/java/org/keycloak/services/resources/AccountService.java
index d343af6..d6cc1b8 100755
--- a/services/src/main/java/org/keycloak/services/resources/AccountService.java
+++ b/services/src/main/java/org/keycloak/services/resources/AccountService.java
@@ -25,7 +25,6 @@ import org.jboss.logging.Logger;
 import org.jboss.resteasy.spi.BadRequestException;
 import org.jboss.resteasy.spi.HttpRequest;
 import org.keycloak.ClientConnection;
-import org.keycloak.OAuth2Constants;
 import org.keycloak.account.AccountPages;
 import org.keycloak.account.AccountProvider;
 import org.keycloak.events.EventBuilder;
@@ -56,8 +55,8 @@ import org.keycloak.services.managers.Auth;
 import org.keycloak.services.managers.AuthenticationManager;
 import org.keycloak.services.managers.ClientSessionCode;
 import org.keycloak.services.messages.Messages;
-import org.keycloak.services.protocol.OpenIdConnectProtocol;
 import org.keycloak.services.resources.flows.Flows;
+import org.keycloak.protocol.oidc.OAuthFlows;
 import org.keycloak.services.resources.flows.OAuthRedirect;
 import org.keycloak.services.resources.flows.Urls;
 import org.keycloak.services.util.CookieHelper;
@@ -658,7 +657,7 @@ public class AccountService {
                     ClientSessionModel clientSession = auth.getClientSession();
                     clientSession.setAction(ClientSessionModel.Action.AUTHENTICATE);
                     clientSession.setRedirectUri(redirectUri);
-                    clientSession.setNote(OpenIdConnectProtocol.STATE_PARAM, UUID.randomUUID().toString());
+                    clientSession.setNote(OAuthFlows.STATE_PARAM, UUID.randomUUID().toString());
                     ClientSessionCode clientSessionCode = new ClientSessionCode(realm, clientSession);
                     return Flows.social(realm, uriInfo, clientConnection, provider)
                             .redirectToSocialProvider(clientSessionCode);
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java b/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java
index 4c2c6e3..3fc6f68 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java
@@ -20,7 +20,6 @@ import org.keycloak.models.SocialLinkModel;
 import org.keycloak.models.UserCredentialModel;
 import org.keycloak.models.UserModel;
 import org.keycloak.models.UserSessionModel;
-import org.keycloak.models.utils.KeycloakModelUtils;
 import org.keycloak.models.utils.ModelToRepresentation;
 import org.keycloak.models.utils.RepresentationToModel;
 import org.keycloak.representations.adapters.action.UserStats;
@@ -31,14 +30,13 @@ import org.keycloak.representations.idm.RoleRepresentation;
 import org.keycloak.representations.idm.SocialLinkRepresentation;
 import org.keycloak.representations.idm.UserRepresentation;
 import org.keycloak.representations.idm.UserSessionRepresentation;
-import org.keycloak.services.managers.AccessCode;
 import org.keycloak.services.managers.ClientSessionCode;
 import org.keycloak.services.managers.RealmManager;
 import org.keycloak.services.managers.ResourceAdminManager;
 import org.keycloak.services.managers.TokenManager;
 import org.keycloak.services.managers.UserManager;
-import org.keycloak.services.protocol.OpenIdConnectProtocol;
 import org.keycloak.services.resources.flows.Flows;
+import org.keycloak.protocol.oidc.OAuthFlows;
 import org.keycloak.services.resources.flows.Urls;
 
 import javax.ws.rs.Consumes;
@@ -897,7 +895,7 @@ public class UsersResource {
         UserSessionModel userSession = session.sessions().createUserSession(realm, user, username, clientConnection.getRemoteAddr(), "form", false);
         //audit.session(userSession);
         ClientSessionModel clientSession = session.sessions().createClientSession(realm, client);
-        clientSession.setAuthMethod(OpenIdConnectProtocol.LOGIN_PAGE_PROTOCOL);
+        clientSession.setAuthMethod(OAuthFlows.LOGIN_PAGE_PROTOCOL);
         clientSession.setRedirectUri(redirect);
         clientSession.setUserSession(userSession);
         ClientSessionCode accessCode = new ClientSessionCode(realm, clientSession);
diff --git a/services/src/main/java/org/keycloak/services/resources/flows/Flows.java b/services/src/main/java/org/keycloak/services/resources/flows/Flows.java
index 2adf26c..01bdb61 100755
--- a/services/src/main/java/org/keycloak/services/resources/flows/Flows.java
+++ b/services/src/main/java/org/keycloak/services/resources/flows/Flows.java
@@ -21,14 +21,11 @@
  */
 package org.keycloak.services.resources.flows;
 
-import org.jboss.resteasy.spi.HttpRequest;
 import org.keycloak.ClientConnection;
 import org.keycloak.login.LoginFormsProvider;
 import org.keycloak.models.ClientModel;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.RealmModel;
-import org.keycloak.services.managers.AuthenticationManager;
-import org.keycloak.services.managers.TokenManager;
 import org.keycloak.social.SocialProvider;
 
 import javax.ws.rs.core.Response;
@@ -46,11 +43,6 @@ public class Flows {
         return session.getProvider(LoginFormsProvider.class).setRealm(realm).setUriInfo(uriInfo).setClient(client);
     }
 
-    public static OAuthFlows oauth(KeycloakSession session, RealmModel realm, HttpRequest request, UriInfo uriInfo, ClientConnection clientConnection, AuthenticationManager authManager,
-            TokenManager tokenManager) {
-        return new OAuthFlows(session, realm, request, uriInfo, clientConnection, authManager, tokenManager);
-    }
-
     public static SocialRedirectFlows social(RealmModel realm, UriInfo uriInfo, ClientConnection clientConnection, SocialProvider provider) {
         return new SocialRedirectFlows(realm, uriInfo, clientConnection, provider);
     }
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 ca5f72f..5fb47e2 100755
--- a/services/src/main/java/org/keycloak/services/resources/RequiredActionsService.java
+++ b/services/src/main/java/org/keycloak/services/resources/RequiredActionsService.java
@@ -24,8 +24,6 @@ package org.keycloak.services.resources;
 import org.jboss.logging.Logger;
 import org.jboss.resteasy.spi.HttpRequest;
 import org.keycloak.ClientConnection;
-import org.keycloak.OAuth2Constants;
-import org.keycloak.events.Event;
 import org.keycloak.events.EventBuilder;
 import org.keycloak.events.Details;
 import org.keycloak.events.Errors;
@@ -42,13 +40,12 @@ import org.keycloak.models.UserModel;
 import org.keycloak.models.UserModel.RequiredAction;
 import org.keycloak.models.UserSessionModel;
 import org.keycloak.models.utils.TimeBasedOTP;
+import org.keycloak.protocol.LoginProtocol;
 import org.keycloak.representations.idm.CredentialRepresentation;
-import org.keycloak.services.managers.AccessCode;
 import org.keycloak.services.managers.AuthenticationManager;
 import org.keycloak.services.managers.ClientSessionCode;
 import org.keycloak.services.managers.TokenManager;
 import org.keycloak.services.messages.Messages;
-import org.keycloak.services.protocol.OpenIdConnectProtocol;
 import org.keycloak.services.resources.flows.Flows;
 import org.keycloak.services.resources.flows.Urls;
 import org.keycloak.services.validation.Validation;
@@ -165,7 +162,7 @@ public class RequiredActionsService {
         String error = Validation.validateUpdateProfileForm(formData);
         if (error != null) {
             return Flows.forms(session, realm, null, uriInfo).setUser(user).setError(error)
-                    .setAccessCode(accessCode.getCode())
+                    .setClientSessionCode(accessCode.getCode())
                     .createResponse(RequiredAction.UPDATE_PROFILE);
         }
 
@@ -212,11 +209,11 @@ public class RequiredActionsService {
         LoginFormsProvider loginForms = Flows.forms(session, realm, null, uriInfo).setUser(user);
         if (Validation.isEmpty(totp)) {
             return loginForms.setError(Messages.MISSING_TOTP)
-                    .setAccessCode(accessCode.getCode())
+                    .setClientSessionCode(accessCode.getCode())
                     .createResponse(RequiredAction.CONFIGURE_TOTP);
         } else if (!new TimeBasedOTP().validate(totp, totpSecret.getBytes())) {
             return loginForms.setError(Messages.INVALID_TOTP)
-                    .setAccessCode(accessCode.getCode())
+                    .setClientSessionCode(accessCode.getCode())
                     .createResponse(RequiredAction.CONFIGURE_TOTP);
         }
 
@@ -257,11 +254,11 @@ public class RequiredActionsService {
         LoginFormsProvider loginForms = Flows.forms(session, realm, null, uriInfo).setUser(user);
         if (Validation.isEmpty(passwordNew)) {
             return loginForms.setError(Messages.MISSING_PASSWORD)
-                    .setAccessCode(accessCode.getCode())
+                    .setClientSessionCode(accessCode.getCode())
                     .createResponse(RequiredAction.UPDATE_PASSWORD);
         } else if (!passwordNew.equals(passwordConfirm)) {
             return loginForms.setError(Messages.NOTMATCH_PASSWORD)
-                    .setAccessCode(accessCode.getCode())
+                    .setClientSessionCode(accessCode.getCode())
                     .createResponse(RequiredAction.UPDATE_PASSWORD);
         }
 
@@ -269,7 +266,7 @@ public class RequiredActionsService {
             session.users().updateCredential(realm, user, UserCredentialModel.password(passwordNew));
         } catch (Exception ape) {
             return loginForms.setError(ape.getMessage())
-                    .setAccessCode(accessCode.getCode())
+                    .setClientSessionCode(accessCode.getCode())
                     .createResponse(RequiredAction.UPDATE_PASSWORD);
         }
 
@@ -330,7 +327,7 @@ public class RequiredActionsService {
             initEvent(clientSession);
 
             return Flows.forms(session, realm, null, uriInfo)
-                    .setAccessCode(accessCode.getCode())
+                    .setClientSessionCode(accessCode.getCode())
                     .setVerifyCode(verifyCode)
                     .setUser(userSession.getUser())
                     .createResponse(RequiredAction.VERIFY_EMAIL);
@@ -356,11 +353,11 @@ public class RequiredActionsService {
                 return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Somebody is trying to illegally change your password.");
             }
             return Flows.forms(session, realm, null, uriInfo)
-                    .setAccessCode(accessCode.getCode())
+                    .setClientSessionCode(accessCode.getCode())
                     .createResponse(RequiredAction.UPDATE_PASSWORD);
         } else {
             return Flows.forms(session, realm, null, uriInfo)
-                    .setAccessCode(code)
+                    .setClientSessionCode(code)
                     .createPasswordReset();
         }
     }
@@ -433,7 +430,7 @@ public class RequiredActionsService {
             } catch (EmailException e) {
                 logger.error("Failed to send password reset email", e);
                 return Flows.forms(this.session, realm, client, uriInfo).setError("emailSendError")
-                        .setAccessCode(accessCode.getCode())
+                        .setClientSessionCode(accessCode.getCode())
                         .createErrorPage();
             }
         }
@@ -442,36 +439,7 @@ public class RequiredActionsService {
     }
 
     private Response redirectOauth(UserModel user, ClientSessionCode accessCode, ClientSessionModel clientSession, UserSessionModel userSession) {
-        if (accessCode == null) {
-            return null;
-        }
-
-        Set<RequiredAction> requiredActions = user.getRequiredActions();
-        if (!requiredActions.isEmpty()) {
-            accessCode.setRequiredAction(requiredActions.iterator().next());
-            return Flows.forms(session, realm, null, uriInfo)
-                    .setAccessCode(accessCode.getCode())
-                    .setUser(user)
-                    .createResponse(requiredActions.iterator().next());
-        } else {
-            logger.debugv("Redirecting to: {0}", clientSession.getRedirectUri());
-            accessCode.setAction(ClientSessionModel.Action.CODE_TO_TOKEN);
-
-            AuthenticationManager authManager = new AuthenticationManager();
-
-            if (!AuthenticationManager.isSessionValid(realm, userSession)) {
-                AuthenticationManager.logout(session, realm, userSession, uriInfo, clientConnection);
-                return Flows.oauth(this.session, realm, request, uriInfo, clientConnection, authManager, tokenManager)
-                        .redirectError(clientSession.getClient(), "access_denied", clientSession.getNote(OpenIdConnectProtocol.STATE_PARAM), clientSession.getRedirectUri());
-            }
-            event.session(userSession);
-
-            event.success();
-
-            return Flows.oauth(this.session, realm, request, uriInfo, clientConnection, authManager, tokenManager)
-                    .redirectAccessCode(accessCode,
-                            userSession, clientSession.getNote(OpenIdConnectProtocol.STATE_PARAM), clientSession.getRedirectUri());
-        }
+        return AuthenticationManager.nextActionAfterAuthentication(session, userSession, clientSession, clientConnection, request, uriInfo, event);
     }
 
     private void initEvent(ClientSessionModel clientSession) {
diff --git a/services/src/main/java/org/keycloak/services/resources/SocialResource.java b/services/src/main/java/org/keycloak/services/resources/SocialResource.java
index 7a8733a..5c0b039 100755
--- a/services/src/main/java/org/keycloak/services/resources/SocialResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/SocialResource.java
@@ -25,12 +25,10 @@ import org.jboss.logging.Logger;
 import org.jboss.resteasy.specimpl.MultivaluedMapImpl;
 import org.jboss.resteasy.spi.HttpRequest;
 import org.keycloak.ClientConnection;
-import org.keycloak.OAuth2Constants;
 import org.keycloak.events.EventBuilder;
 import org.keycloak.events.Details;
 import org.keycloak.events.Errors;
 import org.keycloak.events.EventType;
-import org.keycloak.jose.jws.JWSInput;
 import org.keycloak.models.AccountRoles;
 import org.keycloak.models.ClientModel;
 import org.keycloak.models.ClientSessionModel;
@@ -48,7 +46,6 @@ import org.keycloak.services.managers.AuthenticationManager;
 import org.keycloak.services.managers.RealmManager;
 import org.keycloak.services.managers.TokenManager;
 import org.keycloak.services.resources.flows.Flows;
-import org.keycloak.services.resources.flows.OAuthFlows;
 import org.keycloak.services.resources.flows.Urls;
 import org.keycloak.social.AuthCallback;
 import org.keycloak.social.SocialAccessDeniedException;
@@ -63,7 +60,6 @@ import javax.ws.rs.Path;
 import javax.ws.rs.PathParam;
 import javax.ws.rs.QueryParam;
 import javax.ws.rs.core.Context;
-import javax.ws.rs.core.Cookie;
 import javax.ws.rs.core.HttpHeaders;
 import javax.ws.rs.core.MultivaluedMap;
 import javax.ws.rs.core.Response;
@@ -156,7 +152,7 @@ public class SocialResource {
         } catch (SocialAccessDeniedException e) {
             event.error(Errors.REJECTED_BY_USER);
             clientSession.setAction(ClientSessionModel.Action.AUTHENTICATE);
-            return  Flows.forms(session, realm, clientSession.getClient(), uriInfo).setAccessCode(clientCode.getCode()).setWarning("Access denied").createLogin();
+            return  Flows.forms(session, realm, clientSession.getClient(), uriInfo).setClientSessionCode(clientCode.getCode()).setWarning("Access denied").createLogin();
         } catch (SocialProviderException e) {
             logger.error("Failed to process social callback", e);
             return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Failed to process social callback");
@@ -230,8 +226,8 @@ public class SocialResource {
             event.session(userSession);
             TokenManager.attachClientSession(userSession, clientSession);
 
-            OAuthFlows oauth = Flows.oauth(session, realm, request, uriInfo, clientConnection, new AuthenticationManager(), tokenManager);
-            Response response = oauth.processAccessCode(clientSession, userSession, event);
+            AuthenticationManager authManager = new AuthenticationManager();
+            Response response = authManager.nextActionAfterAuthentication(session, userSession, clientSession, clientConnection, request, uriInfo, event);
             if (session.getTransaction().isActive()) {
                 session.getTransaction().commit();
             }
@@ -239,7 +235,7 @@ public class SocialResource {
         } catch (ModelDuplicateException e) {
             // Assume email is the duplicate as there's nothing else atm
             return Flows.forms(session, realm, clientSession.getClient(), uriInfo)
-                    .setAccessCode(clientCode.getCode())
+                    .setClientSessionCode(clientCode.getCode())
                     .setError("socialEmailExists")
                     .createLogin();
         }
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 55a320c..17ec212 100755
--- a/services/src/main/java/org/keycloak/services/resources/TokenService.java
+++ b/services/src/main/java/org/keycloak/services/resources/TokenService.java
@@ -31,6 +31,7 @@ import org.keycloak.models.UserCredentialModel;
 import org.keycloak.models.UserModel;
 import org.keycloak.models.UserSessionModel;
 import org.keycloak.models.utils.KeycloakModelUtils;
+import org.keycloak.protocol.LoginProtocol;
 import org.keycloak.representations.AccessToken;
 import org.keycloak.representations.AccessTokenResponse;
 import org.keycloak.representations.RefreshToken;
@@ -40,12 +41,10 @@ import org.keycloak.services.managers.AuthenticationManager;
 import org.keycloak.services.managers.AuthenticationManager.AuthenticationStatus;
 import org.keycloak.representations.PasswordToken;
 import org.keycloak.services.managers.ClientSessionCode;
-import org.keycloak.services.managers.ResourceAdminManager;
 import org.keycloak.services.managers.TokenManager;
 import org.keycloak.services.messages.Messages;
-import org.keycloak.services.protocol.OpenIdConnectProtocol;
 import org.keycloak.services.resources.flows.Flows;
-import org.keycloak.services.resources.flows.OAuthFlows;
+import org.keycloak.protocol.oidc.OAuthFlows;
 import org.keycloak.services.resources.flows.Urls;
 import org.keycloak.services.validation.Validation;
 import org.keycloak.util.Base64Url;
@@ -114,8 +113,6 @@ public class TokenService {
     protected ResourceContext resourceContext;
     */
 
-    private ResourceAdminManager resourceAdminManager = new ResourceAdminManager();
-
     public TokenService(RealmModel realm, TokenManager tokenManager, EventBuilder event, AuthenticationManager authManager) {
         this.realm = realm;
         this.tokenManager = tokenManager;
@@ -492,7 +489,7 @@ public class TokenService {
             clientCode.setAction(ClientSessionModel.Action.AUTHENTICATE);
             event.client(clientSession.getClient()).error(Errors.INVALID_USER_CREDENTIALS);
             return Flows.forms(this.session, realm, clientSession.getClient(), uriInfo).setError(Messages.INVALID_USER)
-                    .setAccessCode(clientCode.getCode())
+                    .setClientSessionCode(clientCode.getCode())
                     .createLogin();
         }
 
@@ -511,7 +508,6 @@ public class TokenService {
             event.detail(Details.REMEMBER_ME, "true");
         }
 
-        OAuthFlows oauth = Flows.oauth(session, realm, request, uriInfo, clientConnection, authManager, tokenManager);
 
         ClientModel client = clientSession.getClient();
         if (client == null) {
@@ -525,7 +521,12 @@ public class TokenService {
 
         if (formData.containsKey("cancel")) {
             event.error(Errors.REJECTED_BY_USER);
-            return oauth.redirectError(client, "access_denied", clientSession.getNote(OpenIdConnectProtocol.STATE_PARAM), clientSession.getRedirectUri());
+            LoginProtocol protocol = session.getProvider(LoginProtocol.class, clientSession.getAuthMethod());
+            protocol.setRealm(realm)
+                    .setRequest(request)
+                    .setUriInfo(uriInfo)
+                    .setClientConnection(clientConnection);
+            return protocol.cancelLogin(clientSession);
         }
 
         AuthenticationStatus status = authManager.authenticateForm(session, clientConnection, realm, formData);
@@ -547,20 +548,19 @@ public class TokenService {
                 UserSessionModel userSession = session.sessions().createUserSession(realm, user, username, clientConnection.getRemoteAddr(), "form", remember);
                 TokenManager.attachClientSession(userSession, clientSession);
 		        event.session(userSession);
-
-                return oauth.processAccessCode(clientSession, userSession, event);
+                return authManager.nextActionAfterAuthentication(session, userSession, clientSession, clientConnection, request, uriInfo, event);
             case ACCOUNT_TEMPORARILY_DISABLED:
                 event.error(Errors.USER_TEMPORARILY_DISABLED);
                 return Flows.forms(this.session, realm, client, uriInfo)
                         .setError(Messages.ACCOUNT_TEMPORARILY_DISABLED)
                         .setFormData(formData)
-                        .setAccessCode(clientCode.getCode())
+                        .setClientSessionCode(clientCode.getCode())
                         .createLogin();
             case ACCOUNT_DISABLED:
                 event.error(Errors.USER_DISABLED);
                 return Flows.forms(this.session, realm, client, uriInfo)
                         .setError(Messages.ACCOUNT_DISABLED)
-                        .setAccessCode(clientCode.getCode())
+                        .setClientSessionCode(clientCode.getCode())
                         .setFormData(formData).createLogin();
             case MISSING_TOTP:
                 formData.remove(CredentialRepresentation.PASSWORD);
@@ -570,19 +570,19 @@ public class TokenService {
 
                 return Flows.forms(this.session, realm, client, uriInfo)
                         .setFormData(formData)
-                        .setAccessCode(clientCode.getCode())
+                        .setClientSessionCode(clientCode.getCode())
                         .createLoginTotp();
             case INVALID_USER:
                 event.error(Errors.USER_NOT_FOUND);
                 return Flows.forms(this.session, realm, client, uriInfo).setError(Messages.INVALID_USER)
                         .setFormData(formData)
-                        .setAccessCode(clientCode.getCode())
+                        .setClientSessionCode(clientCode.getCode())
                         .createLogin();
             default:
                 event.error(Errors.INVALID_USER_CREDENTIALS);
                 return Flows.forms(this.session, realm, client, uriInfo).setError(Messages.INVALID_USER)
                         .setFormData(formData)
-                        .setAccessCode(clientCode.getCode())
+                        .setClientSessionCode(clientCode.getCode())
                         .createLogin();
         }
     }
@@ -642,8 +642,6 @@ public class TokenService {
                 .detail(Details.EMAIL, email)
                 .detail(Details.REGISTER_METHOD, "form");
 
-        OAuthFlows oauth = Flows.oauth(session, realm, request, uriInfo, clientConnection, authManager, tokenManager);
-
         if (!realm.isEnabled()) {
             event.error(Errors.REALM_DISABLED);
             return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Realm not enabled");
@@ -676,7 +674,7 @@ public class TokenService {
             return Flows.forms(session, realm, client, uriInfo)
                     .setError(error)
                     .setFormData(formData)
-                    .setAccessCode(clientCode.getCode())
+                    .setClientSessionCode(clientCode.getCode())
                     .createRegistration();
         }
 
@@ -686,7 +684,7 @@ public class TokenService {
             return Flows.forms(session, realm, client, uriInfo)
                     .setError(Messages.USERNAME_EXISTS)
                     .setFormData(formData)
-                    .setAccessCode(clientCode.getCode())
+                    .setClientSessionCode(clientCode.getCode())
                     .createRegistration();
         }
 
@@ -696,7 +694,7 @@ public class TokenService {
             return Flows.forms(session, realm, client, uriInfo)
                     .setError(Messages.EMAIL_EXISTS)
                     .setFormData(formData)
-                    .setAccessCode(clientCode.getCode())
+                    .setClientSessionCode(clientCode.getCode())
                     .createRegistration();
         }
 
@@ -727,7 +725,7 @@ public class TokenService {
                 user.addRequiredAction(UserModel.RequiredAction.UPDATE_PASSWORD);
                 return Flows.forms(session, realm, client, uriInfo)
                         .setError(passwordUpdateError)
-                        .setAccessCode(clientCode.getCode())
+                        .setClientSessionCode(clientCode.getCode())
                         .createResponse(UserModel.RequiredAction.UPDATE_PASSWORD);
             }
         }
@@ -981,15 +979,15 @@ public class TokenService {
                     return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Invalid code, please login again through your application.");
                 }
                 clientSession = clientCode.getClientSession();
-                if (!clientSession.getAuthMethod().equals(OpenIdConnectProtocol.LOGIN_PAGE_PROTOCOL)) {
+                if (!clientSession.getAuthMethod().equals(OAuthFlows.LOGIN_PAGE_PROTOCOL)) {
                     event.error(Errors.INVALID_CODE);
                     return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Invalid protocol, please login again through your application.");
                 }
-                state = clientSession.getNote(OpenIdConnectProtocol.STATE_PARAM);
-                scopeParam = clientSession.getNote(OpenIdConnectProtocol.SCOPE_PARAM);
-                responseType = clientSession.getNote(OpenIdConnectProtocol.RESPONSE_TYPE_PARAM);
-                loginHint = clientSession.getNote(OpenIdConnectProtocol.LOGIN_HINT_PARAM);
-                prompt = clientSession.getNote(OpenIdConnectProtocol.PROMPT_PARAM);
+                state = clientSession.getNote(OAuthFlows.STATE_PARAM);
+                scopeParam = clientSession.getNote(OAuthFlows.SCOPE_PARAM);
+                responseType = clientSession.getNote(OAuthFlows.RESPONSE_TYPE_PARAM);
+                loginHint = clientSession.getNote(OAuthFlows.LOGIN_HINT_PARAM);
+                prompt = clientSession.getNote(OAuthFlows.PROMPT_PARAM);
             } else {
                 if (state == null) {
                     event.error(Errors.STATE_PARAM_NOT_FOUND);
@@ -1020,14 +1018,14 @@ public class TokenService {
                     return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Invalid redirect_uri.");
                 }
                 clientSession = session.sessions().createClientSession(realm, client);
-                clientSession.setAuthMethod(OpenIdConnectProtocol.LOGIN_PAGE_PROTOCOL);
+                clientSession.setAuthMethod(OAuthFlows.LOGIN_PAGE_PROTOCOL);
                 clientSession.setRedirectUri(redirect);
                 clientSession.setAction(ClientSessionModel.Action.AUTHENTICATE);
-                clientSession.setNote(OpenIdConnectProtocol.STATE_PARAM, state);
-                if (scopeParam != null) clientSession.setNote(OpenIdConnectProtocol.SCOPE_PARAM, scopeParam);
-                if (responseType != null) clientSession.setNote(OpenIdConnectProtocol.RESPONSE_TYPE_PARAM, responseType);
-                if (loginHint != null) clientSession.setNote(OpenIdConnectProtocol.LOGIN_HINT_PARAM, loginHint);
-                if (prompt != null) clientSession.setNote(OpenIdConnectProtocol.PROMPT_PARAM, prompt);
+                clientSession.setNote(OAuthFlows.STATE_PARAM, state);
+                if (scopeParam != null) clientSession.setNote(OAuthFlows.SCOPE_PARAM, scopeParam);
+                if (responseType != null) clientSession.setNote(OAuthFlows.RESPONSE_TYPE_PARAM, responseType);
+                if (loginHint != null) clientSession.setNote(OAuthFlows.LOGIN_HINT_PARAM, loginHint);
+                if (prompt != null) clientSession.setNote(OAuthFlows.PROMPT_PARAM, prompt);
             }
             return null;
         }
@@ -1051,13 +1049,13 @@ public class TokenService {
     @Path("login")
     @GET
     public Response loginPage(@QueryParam("code") String code,
-                              @QueryParam(OpenIdConnectProtocol.RESPONSE_TYPE_PARAM) String responseType,
-                              @QueryParam(OpenIdConnectProtocol.REDIRECT_URI_PARAM) String redirect,
-                              @QueryParam(OpenIdConnectProtocol.CLIENT_ID_PARAM) String clientId,
-                              @QueryParam(OpenIdConnectProtocol.SCOPE_PARAM) String scopeParam,
-                              @QueryParam(OpenIdConnectProtocol.STATE_PARAM) String state,
-                              @QueryParam(OpenIdConnectProtocol.PROMPT_PARAM) String prompt,
-                              @QueryParam(OpenIdConnectProtocol.LOGIN_HINT_PARAM) String loginHint) {
+                              @QueryParam(OAuthFlows.RESPONSE_TYPE_PARAM) String responseType,
+                              @QueryParam(OAuthFlows.REDIRECT_URI_PARAM) String redirect,
+                              @QueryParam(OAuthFlows.CLIENT_ID_PARAM) String clientId,
+                              @QueryParam(OAuthFlows.SCOPE_PARAM) String scopeParam,
+                              @QueryParam(OAuthFlows.STATE_PARAM) String state,
+                              @QueryParam(OAuthFlows.PROMPT_PARAM) String prompt,
+                              @QueryParam(OAuthFlows.LOGIN_HINT_PARAM) String loginHint) {
         event.event(EventType.LOGIN);
         FrontPageInitializer pageInitializer = new FrontPageInitializer();
         pageInitializer.code = code;
@@ -1081,23 +1079,23 @@ public class TokenService {
         loginHint = pageInitializer.loginHint;
 
 
-        OAuthFlows oauth = Flows.oauth(session, realm, request, uriInfo, clientConnection, authManager, tokenManager);
 
         AuthenticationManager.AuthResult authResult = authManager.authenticateIdentityCookie(session, realm, uriInfo, clientConnection, headers);
         if (authResult != null) {
             UserModel user = authResult.getUser();
-            UserSessionModel session = authResult.getSession();
-            TokenManager.attachClientSession(session, clientSession);
-            event.user(user).session(session).detail(Details.AUTH_METHOD, "sso");
-            return oauth.processAccessCode(clientSession, session, event);
+            UserSessionModel userSession = authResult.getSession();
+            TokenManager.attachClientSession(userSession, clientSession);
+            event.user(user).session(userSession).detail(Details.AUTH_METHOD, "sso");
+            return authManager.nextActionAfterAuthentication(session, userSession, clientSession, clientConnection, request, uriInfo, event);
         }
 
         if (prompt != null && prompt.equals("none")) {
-            return oauth.redirectError(clientSession.getClient(), "access_denied", state, redirect);
+            OAuthFlows oauth = new OAuthFlows(session, realm, request, uriInfo, clientConnection);
+            return oauth.cancelLogin(clientSession);
         }
 
         LoginFormsProvider forms = Flows.forms(session, realm, clientSession.getClient(), uriInfo)
-                .setAccessCode(new ClientSessionCode(realm, clientSession).getCode());
+                .setClientSessionCode(new ClientSessionCode(realm, clientSession).getCode());
 
         String rememberMeUsername = null;
         if (realm.isRememberMe()) {
@@ -1136,11 +1134,11 @@ public class TokenService {
     @Path("registrations")
     @GET
     public Response registerPage(@QueryParam("code") String code,
-                                 @QueryParam("response_type") String responseType,
-                                 @QueryParam(OpenIdConnectProtocol.REDIRECT_URI_PARAM) String redirect,
-                                 @QueryParam(OpenIdConnectProtocol.CLIENT_ID_PARAM) String clientId,
-                                 @QueryParam("scope") String scopeParam,
-                                 @QueryParam("state") String state) {
+                                 @QueryParam(OAuthFlows.RESPONSE_TYPE_PARAM) String responseType,
+                                 @QueryParam(OAuthFlows.REDIRECT_URI_PARAM) String redirect,
+                                 @QueryParam(OAuthFlows.CLIENT_ID_PARAM) String clientId,
+                                 @QueryParam(OAuthFlows.SCOPE_PARAM) String scopeParam,
+                                 @QueryParam(OAuthFlows.STATE_PARAM) String state) {
         event.event(EventType.REGISTER);
         if (!realm.isRegistrationAllowed()) {
             event.error(Errors.REGISTRATION_DISABLED);
@@ -1162,7 +1160,7 @@ public class TokenService {
         authManager.expireIdentityCookie(realm, uriInfo, clientConnection);
 
         return Flows.forms(session, realm, clientSession.getClient(), uriInfo)
-                .setAccessCode(new ClientSessionCode(realm, clientSession).getCode())
+                .setClientSessionCode(new ClientSessionCode(realm, clientSession).getCode())
                 .createRegistration();
     }
 
@@ -1175,7 +1173,7 @@ public class TokenService {
     @Path("logout")
     @GET
     @NoCache
-    public Response logout(final @QueryParam(OpenIdConnectProtocol.REDIRECT_URI_PARAM) String redirectUri) {
+    public Response logout(final @QueryParam(OAuthFlows.REDIRECT_URI_PARAM) String redirectUri) {
         event.event(EventType.LOGOUT);
         if (redirectUri != null) {
             event.detail(Details.REDIRECT_URI, redirectUri);
@@ -1189,7 +1187,6 @@ public class TokenService {
         if (redirectUri != null) {
             String validatedRedirect = verifyRealmRedirectUri(uriInfo, redirectUri, realm);
             if (validatedRedirect == null) {
-                OAuthFlows oauth = Flows.oauth(session, realm, request, uriInfo, clientConnection, authManager, tokenManager);
                 return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Invalid redirect uri.");
             }
             return Response.status(302).location(UriBuilder.fromUri(validatedRedirect).build()).build();
@@ -1267,7 +1264,6 @@ public class TokenService {
     public Response processOAuth(final MultivaluedMap<String, String> formData) {
         event.event(EventType.LOGIN).detail(Details.RESPONSE_TYPE, "code");
 
-        OAuthFlows oauth = Flows.oauth(session, realm, request, uriInfo, clientConnection, authManager, tokenManager);
 
         if (!checkSsl()) {
             return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "HTTPS required");
@@ -1306,15 +1302,19 @@ public class TokenService {
         }
         event.session(userSession);
 
+        LoginProtocol protocol = session.getProvider(LoginProtocol.class, clientSession.getAuthMethod());
+        protocol.setRealm(realm)
+                .setRequest(request)
+                .setUriInfo(uriInfo)
+                .setClientConnection(clientConnection);
         if (formData.containsKey("cancel")) {
             event.error(Errors.REJECTED_BY_USER);
-            return redirectAccessDenied(redirect, clientSession.getNote(OpenIdConnectProtocol.STATE_PARAM));
+            return protocol.consentDenied(clientSession);
         }
 
         event.success();
 
-        accessCode.setAction(ClientSessionModel.Action.CODE_TO_TOKEN);
-        return oauth.redirectAccessCode(accessCode, userSession, clientSession.getNote(OpenIdConnectProtocol.STATE_PARAM), redirect);
+        return authManager.redirectAfterSuccessfulFlow(session, realm, userSession, clientSession, request, uriInfo, clientConnection);
     }
 
     @Path("oauth/oob")
@@ -1322,20 +1322,12 @@ public class TokenService {
     public Response installedAppUrnCallback(final @QueryParam("code") String code, final @QueryParam("error") String error, final @QueryParam("error_description") String errorDescription) {
         LoginFormsProvider forms = Flows.forms(session, realm, null, uriInfo);
         if (code != null) {
-            return forms.setAccessCode(code).createCode();
+            return forms.setClientSessionCode(code).createCode();
         } else {
             return forms.setError(error).createCode();
         }
     }
 
-    protected Response redirectAccessDenied(String redirect, String state) {
-        UriBuilder redirectUri = UriBuilder.fromUri(redirect).queryParam(OAuth2Constants.ERROR, "access_denied");
-        if (state != null)
-            redirectUri.queryParam(OAuth2Constants.STATE, state);
-        Response.ResponseBuilder location = Response.status(302).location(redirectUri.build());
-        return location.build();
-    }
-
     public static boolean matchesRedirects(Set<String> validRedirects, String redirect) {
         for (String validRedirect : validRedirects) {
             if (validRedirect.endsWith("*")) {
diff --git a/services/src/main/resources/META-INF/services/org.keycloak.protocol.LoginProtocolFactory b/services/src/main/resources/META-INF/services/org.keycloak.protocol.LoginProtocolFactory
new file mode 100755
index 0000000..ca8ba63
--- /dev/null
+++ b/services/src/main/resources/META-INF/services/org.keycloak.protocol.LoginProtocolFactory
@@ -0,0 +1 @@
+org.keycloak.protocol.oidc.OpenIDConnectFactory
\ No newline at end of file
diff --git a/services/src/main/resources/META-INF/services/org.keycloak.provider.Spi b/services/src/main/resources/META-INF/services/org.keycloak.provider.Spi
new file mode 100755
index 0000000..62aa8e7
--- /dev/null
+++ b/services/src/main/resources/META-INF/services/org.keycloak.provider.Spi
@@ -0,0 +1 @@
+org.keycloak.protocol.LoginProtocolSpi
\ No newline at end of file
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserSessionProviderTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserSessionProviderTest.java
index 9cda36b..5d4dce0 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserSessionProviderTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserSessionProviderTest.java
@@ -10,7 +10,7 @@ import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.UserModel;
 import org.keycloak.models.UserSessionModel;
-import org.keycloak.services.protocol.OpenIdConnectProtocol;
+import org.keycloak.protocol.oidc.OAuthFlows;
 import org.keycloak.testsuite.rule.KeycloakRule;
 import org.keycloak.util.Time;
 
@@ -74,7 +74,7 @@ public class UserSessionProviderTest {
         assertEquals(realm.findClient("test-app").getClientId(), session.getClient().getClientId());
         assertEquals(sessions[0].getId(), session.getUserSession().getId());
         assertEquals("http://redirect", session.getRedirectUri());
-        assertEquals("state", session.getNote(OpenIdConnectProtocol.STATE_PARAM));
+        assertEquals("state", session.getNote(OAuthFlows.STATE_PARAM));
         assertEquals(2, session.getRoles().size());
         assertTrue(session.getRoles().contains("one"));
         assertTrue(session.getRoles().contains("two"));
@@ -250,7 +250,7 @@ public class UserSessionProviderTest {
             clientSession.setUserSession(userSession);
             clientSession.setRedirectUri("http://redirect");
             clientSession.setRoles(new HashSet<String>());
-            clientSession.setNote(OpenIdConnectProtocol.STATE_PARAM, "state");
+            clientSession.setNote(OAuthFlows.STATE_PARAM, "state");
         }
 
         resetSession();
@@ -289,7 +289,7 @@ public class UserSessionProviderTest {
         ClientSessionModel clientSession = session.sessions().createClientSession(realm, client);
         if (userSession != null) clientSession.setUserSession(userSession);
         clientSession.setRedirectUri(redirect);
-        if (state != null) clientSession.setNote(OpenIdConnectProtocol.STATE_PARAM, state);
+        if (state != null) clientSession.setNote(OAuthFlows.STATE_PARAM, state);
         if (roles != null) clientSession.setRoles(roles);
         return clientSession;
     }