keycloak-aplcache

handle page refresh better

6/18/2015 9:01:12 PM

Changes

services/src/main/java/org/keycloak/authentication/Authenticator2.java 18(+0 -18)

Details

diff --git a/model/api/src/main/java/org/keycloak/models/ClientSessionModel.java b/model/api/src/main/java/org/keycloak/models/ClientSessionModel.java
index 01f524a..cd724ef 100755
--- a/model/api/src/main/java/org/keycloak/models/ClientSessionModel.java
+++ b/model/api/src/main/java/org/keycloak/models/ClientSessionModel.java
@@ -32,9 +32,9 @@ public interface ClientSessionModel {
     public Set<String> getProtocolMappers();
     public void setProtocolMappers(Set<String> protocolMappers);
 
-    public Map<String, UserSessionModel.AuthenticatorStatus> getAuthenticators();
-    public void setAuthenticatorStatus(String authenticator, UserSessionModel.AuthenticatorStatus status);
-    public void setAuthenticatorStatus(Map<String, UserSessionModel.AuthenticatorStatus> status);
+    public Map<String, ExecutionStatus> getExecutionStatus();
+    public void setExecutionStatus(String authenticator, ExecutionStatus status);
+    public void clearExecutionStatus();
     public UserModel getAuthenticatedUser();
     public void setAuthenticatedUser(UserModel user);
 
@@ -67,6 +67,8 @@ public interface ClientSessionModel {
      */
     public Map<String, String> getUserSessionNotes();
 
+    public void clearUserSessionNotes();
+
     public static enum Action {
         OAUTH_GRANT,
         CODE_TO_TOKEN,
@@ -80,4 +82,12 @@ public interface ClientSessionModel {
         LOGGED_OUT
     }
 
+    public enum ExecutionStatus {
+        FAILED,
+        SUCCESS,
+        SETUP_REQUIRED,
+        ATTEMPTED,
+        SKIPPED,
+        CHALLENGED
+    }
 }
diff --git a/model/api/src/main/java/org/keycloak/models/UserSessionModel.java b/model/api/src/main/java/org/keycloak/models/UserSessionModel.java
index 6af29cf..516ba26 100755
--- a/model/api/src/main/java/org/keycloak/models/UserSessionModel.java
+++ b/model/api/src/main/java/org/keycloak/models/UserSessionModel.java
@@ -37,15 +37,6 @@ public interface UserSessionModel {
 
     List<ClientSessionModel> getClientSessions();
 
-    public static enum AuthenticatorStatus {
-        FAILED,
-        SUCCESS,
-        SETUP_REQUIRED,
-        ATTEMPTED,
-        SKIPPED,
-        CHALLENGED
-    }
-
     public String getNote(String name);
     public void setNote(String name, String value);
     public void removeNote(String name);
diff --git a/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/ClientSessionAdapter.java b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/ClientSessionAdapter.java
index 78be9ee..a00b539 100755
--- a/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/ClientSessionAdapter.java
+++ b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/ClientSessionAdapter.java
@@ -185,23 +185,32 @@ public class ClientSessionAdapter implements ClientSessionModel {
         return copy;
     }
 
+    @Override
+    public void clearUserSessionNotes() {
+        entity.setUserSessionNotes(new HashMap<String, String>());
+        update();
+
+    }
+
     void update() {
         provider.getTx().replace(cache, entity.getId(), entity);
     }
     @Override
-    public Map<String, UserSessionModel.AuthenticatorStatus> getAuthenticators() {
+    public Map<String, ExecutionStatus> getExecutionStatus() {
         return entity.getAuthenticatorStatus();
     }
 
     @Override
-    public void setAuthenticatorStatus(String authenticator, UserSessionModel.AuthenticatorStatus status) {
+    public void setExecutionStatus(String authenticator, ExecutionStatus status) {
         entity.getAuthenticatorStatus().put(authenticator, status);
+        update();
 
     }
 
     @Override
-    public void setAuthenticatorStatus(Map<String, UserSessionModel.AuthenticatorStatus> status) {
-        entity.setAuthenticatorStatus(status);
+    public void clearExecutionStatus() {
+        entity.getAuthenticatorStatus().clear();
+        update();
     }
 
     @Override
@@ -211,6 +220,7 @@ public class ClientSessionAdapter implements ClientSessionModel {
     @Override
     public void setAuthenticatedUser(UserModel user) {
         entity.setAuthUserId(user.getId());
+        update();
 
     }
 
diff --git a/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/ClientSessionEntity.java b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/ClientSessionEntity.java
index 340bf92..3cb6614 100755
--- a/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/ClientSessionEntity.java
+++ b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/ClientSessionEntity.java
@@ -1,7 +1,6 @@
 package org.keycloak.models.sessions.infinispan.entities;
 
 import org.keycloak.models.ClientSessionModel;
-import org.keycloak.models.UserSessionModel;
 
 import java.util.HashMap;
 import java.util.Map;
@@ -30,7 +29,7 @@ public class ClientSessionEntity extends SessionEntity {
     private Set<String> protocolMappers;
     private Map<String, String> notes;
     private Map<String, String> userSessionNotes;
-    private Map<String, UserSessionModel.AuthenticatorStatus> authenticatorStatus = new HashMap<>();
+    private Map<String, ClientSessionModel.ExecutionStatus> authenticatorStatus = new HashMap<>();
     private String authUserId;
 
     public String getClient() {
@@ -113,11 +112,11 @@ public class ClientSessionEntity extends SessionEntity {
         this.notes = notes;
     }
 
-    public Map<String, UserSessionModel.AuthenticatorStatus> getAuthenticatorStatus() {
+    public Map<String, ClientSessionModel.ExecutionStatus> getAuthenticatorStatus() {
         return authenticatorStatus;
     }
 
-    public void setAuthenticatorStatus(Map<String, UserSessionModel.AuthenticatorStatus> authenticatorStatus) {
+    public void setAuthenticatorStatus(Map<String, ClientSessionModel.ExecutionStatus> authenticatorStatus) {
         this.authenticatorStatus = authenticatorStatus;
     }
 
diff --git a/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/ClientSessionAdapter.java b/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/ClientSessionAdapter.java
index 2ce7034..e8498e9 100755
--- a/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/ClientSessionAdapter.java
+++ b/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/ClientSessionAdapter.java
@@ -6,6 +6,7 @@ import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.UserModel;
 import org.keycloak.models.UserSessionModel;
+import org.keycloak.models.sessions.jpa.entities.ClientSessionAuthStatusEntity;
 import org.keycloak.models.sessions.jpa.entities.ClientSessionEntity;
 import org.keycloak.models.sessions.jpa.entities.ClientSessionNoteEntity;
 import org.keycloak.models.sessions.jpa.entities.ClientSessionProtocolMapperEntity;
@@ -107,6 +108,17 @@ public class ClientSessionAdapter implements ClientSessionModel {
     }
 
     @Override
+    public void clearUserSessionNotes() {
+        Iterator<ClientUserSessionNoteEntity> it = entity.getUserSessionNotes().iterator();
+        while (it.hasNext()) {
+            ClientUserSessionNoteEntity attr = it.next();
+            it.remove();
+            em.remove(attr);
+        }
+
+    }
+
+    @Override
     public String getId() {
         return entity.getId();
     }
@@ -242,27 +254,44 @@ public class ClientSessionAdapter implements ClientSessionModel {
     }
 
     @Override
-    public Map<String, UserSessionModel.AuthenticatorStatus> getAuthenticators() {
-        return null;
+    public Map<String, ExecutionStatus> getExecutionStatus() {
+        Map<String, ExecutionStatus> result = new HashMap<>();
+        for (ClientSessionAuthStatusEntity status : entity.getAuthanticatorStatus()) {
+            result.put(status.getAuthenticator(), status.getStatus());
+        }
+        return result;
     }
 
     @Override
-    public void setAuthenticatorStatus(String authenticator, UserSessionModel.AuthenticatorStatus status) {
+    public void setExecutionStatus(String authenticator, ExecutionStatus status) {
+        ClientSessionAuthStatusEntity authStatus = new ClientSessionAuthStatusEntity();
+        authStatus.setAuthenticator(authenticator);
+        authStatus.setClientSession(entity);
+        authStatus.setStatus(status);
+        em.persist(authStatus);
+        entity.getAuthanticatorStatus().add(authStatus);
+        em.flush();
+
 
     }
 
     @Override
-    public void setAuthenticatorStatus(Map<String, UserSessionModel.AuthenticatorStatus> status) {
-
+    public void clearExecutionStatus() {
+        Iterator<ClientSessionAuthStatusEntity> iterator = entity.getAuthanticatorStatus().iterator();
+        while (iterator.hasNext()) {
+            ClientSessionAuthStatusEntity authStatus = iterator.next();
+            iterator.remove();
+            em.remove(authStatus);
+        }
     }
 
     @Override
     public UserModel getAuthenticatedUser() {
-        return null;
+        return session.users().getUserById(entity.getUserId(), realm);
     }
 
     @Override
     public void setAuthenticatedUser(UserModel user) {
-
+        entity.setUserId(user.getId());
     }
 }
diff --git a/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/entities/ClientSessionAuthStatusEntity.java b/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/entities/ClientSessionAuthStatusEntity.java
index 49afbab..e8dea7a 100755
--- a/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/entities/ClientSessionAuthStatusEntity.java
+++ b/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/entities/ClientSessionAuthStatusEntity.java
@@ -1,6 +1,6 @@
 package org.keycloak.models.sessions.jpa.entities;
 
-import org.keycloak.models.UserSessionModel;
+import org.keycloak.models.ClientSessionModel;
 
 import javax.persistence.Column;
 import javax.persistence.Entity;
@@ -39,7 +39,7 @@ public class ClientSessionAuthStatusEntity {
     @Column(name = "AUTHENTICATOR")
     protected String authenticator;
     @Column(name = "STATUS")
-    protected UserSessionModel.AuthenticatorStatus status;
+    protected ClientSessionModel.ExecutionStatus status;
 
     public String getAuthenticator() {
         return authenticator;
@@ -49,11 +49,11 @@ public class ClientSessionAuthStatusEntity {
         this.authenticator = authenticator;
     }
 
-    public UserSessionModel.AuthenticatorStatus getStatus() {
+    public ClientSessionModel.ExecutionStatus getStatus() {
         return status;
     }
 
-    public void setStatus(UserSessionModel.AuthenticatorStatus status) {
+    public void setStatus(ClientSessionModel.ExecutionStatus status) {
         this.status = status;
     }
 
diff --git a/model/sessions-mem/src/main/java/org/keycloak/models/sessions/mem/ClientSessionAdapter.java b/model/sessions-mem/src/main/java/org/keycloak/models/sessions/mem/ClientSessionAdapter.java
index 0e0647f..f9e52d0 100755
--- a/model/sessions-mem/src/main/java/org/keycloak/models/sessions/mem/ClientSessionAdapter.java
+++ b/model/sessions-mem/src/main/java/org/keycloak/models/sessions/mem/ClientSessionAdapter.java
@@ -155,19 +155,24 @@ public class ClientSessionAdapter implements ClientSessionModel {
     }
 
     @Override
-    public Map<String, UserSessionModel.AuthenticatorStatus> getAuthenticators() {
+    public Map<String, ExecutionStatus> getExecutionStatus() {
         return entity.getAuthenticatorStatus();
     }
 
     @Override
-    public void setAuthenticatorStatus(String authenticator, UserSessionModel.AuthenticatorStatus status) {
+    public void setExecutionStatus(String authenticator, ExecutionStatus status) {
         entity.getAuthenticatorStatus().put(authenticator, status);
 
     }
 
     @Override
-    public void setAuthenticatorStatus(Map<String, UserSessionModel.AuthenticatorStatus> status) {
-        entity.setAuthenticatorStatus(status);
+    public void clearExecutionStatus() {
+        entity.getAuthenticatorStatus().clear();
+    }
+
+    @Override
+    public void clearUserSessionNotes() {
+        entity.getUserSessionNotes().clear();
     }
 
     @Override
diff --git a/model/sessions-mem/src/main/java/org/keycloak/models/sessions/mem/entities/ClientSessionEntity.java b/model/sessions-mem/src/main/java/org/keycloak/models/sessions/mem/entities/ClientSessionEntity.java
index e76f624..da5e050 100755
--- a/model/sessions-mem/src/main/java/org/keycloak/models/sessions/mem/entities/ClientSessionEntity.java
+++ b/model/sessions-mem/src/main/java/org/keycloak/models/sessions/mem/entities/ClientSessionEntity.java
@@ -1,7 +1,6 @@
 package org.keycloak.models.sessions.mem.entities;
 
 import org.keycloak.models.ClientSessionModel;
-import org.keycloak.models.UserSessionModel;
 
 import java.util.HashMap;
 import java.util.Map;
@@ -15,7 +14,7 @@ public class ClientSessionEntity {
     private String id;
     private String clientId;
     private String realmId;
-    private Map<String, UserSessionModel.AuthenticatorStatus> authenticatorStatus = new HashMap<>();
+    private Map<String, ClientSessionModel.ExecutionStatus> authenticatorStatus = new HashMap<>();
     private String authUserId;
 
     private UserSessionEntity session;
@@ -122,11 +121,11 @@ public class ClientSessionEntity {
         this.authUserId = authUserId;
     }
 
-    public Map<String, UserSessionModel.AuthenticatorStatus> getAuthenticatorStatus() {
+    public Map<String, ClientSessionModel.ExecutionStatus> getAuthenticatorStatus() {
         return authenticatorStatus;
     }
 
-    public void setAuthenticatorStatus(Map<String, UserSessionModel.AuthenticatorStatus> authenticatorStatus) {
+    public void setAuthenticatorStatus(Map<String, ClientSessionModel.ExecutionStatus> authenticatorStatus) {
         this.authenticatorStatus = authenticatorStatus;
     }
 
diff --git a/model/sessions-mongo/src/main/java/org/keycloak/models/sessions/mongo/ClientSessionAdapter.java b/model/sessions-mongo/src/main/java/org/keycloak/models/sessions/mongo/ClientSessionAdapter.java
index ad1d0d7..4ad1d51 100755
--- a/model/sessions-mongo/src/main/java/org/keycloak/models/sessions/mongo/ClientSessionAdapter.java
+++ b/model/sessions-mongo/src/main/java/org/keycloak/models/sessions/mongo/ClientSessionAdapter.java
@@ -171,22 +171,26 @@ public class ClientSessionAdapter extends AbstractMongoAdapter<MongoClientSessio
     }
 
     @Override
-    public Map<String, UserSessionModel.AuthenticatorStatus> getAuthenticators() {
+    public Map<String, ExecutionStatus> getExecutionStatus() {
         return entity.getAuthenticatorStatus();
     }
 
     @Override
-    public void setAuthenticatorStatus(String authenticator, UserSessionModel.AuthenticatorStatus status) {
+    public void setExecutionStatus(String authenticator, ExecutionStatus status) {
         entity.getAuthenticatorStatus().put(authenticator, status);
         updateMongoEntity();
 
     }
 
     @Override
-    public void setAuthenticatorStatus(Map<String, UserSessionModel.AuthenticatorStatus> status) {
-        entity.setAuthenticatorStatus(status);
+    public void clearExecutionStatus() {
+        entity.getAuthenticatorStatus().clear();
         updateMongoEntity();
+    }
 
+    @Override
+    public void clearUserSessionNotes() {
+        entity.getUserSessionNotes().clear();
     }
 
     @Override
diff --git a/model/sessions-mongo/src/main/java/org/keycloak/models/sessions/mongo/entities/MongoClientSessionEntity.java b/model/sessions-mongo/src/main/java/org/keycloak/models/sessions/mongo/entities/MongoClientSessionEntity.java
index 21831b6..de7bed3 100755
--- a/model/sessions-mongo/src/main/java/org/keycloak/models/sessions/mongo/entities/MongoClientSessionEntity.java
+++ b/model/sessions-mongo/src/main/java/org/keycloak/models/sessions/mongo/entities/MongoClientSessionEntity.java
@@ -4,7 +4,6 @@ import org.keycloak.connections.mongo.api.MongoCollection;
 import org.keycloak.connections.mongo.api.MongoIdentifiableEntity;
 import org.keycloak.connections.mongo.api.context.MongoStoreInvocationContext;
 import org.keycloak.models.ClientSessionModel;
-import org.keycloak.models.UserSessionModel;
 import org.keycloak.models.entities.AbstractIdentifiableEntity;
 
 import java.util.HashMap;
@@ -31,7 +30,7 @@ public class MongoClientSessionEntity extends AbstractIdentifiableEntity impleme
     private List<String> protocolMappers;
     private Map<String, String> notes = new HashMap<String, String>();
     private Map<String, String> userSessionNotes = new HashMap<String, String>();
-    private Map<String, UserSessionModel.AuthenticatorStatus> authenticatorStatus = new HashMap<>();
+    private Map<String, ClientSessionModel.ExecutionStatus> authenticatorStatus = new HashMap<>();
     private String authUserId;
 
     public String getId() {
@@ -130,11 +129,11 @@ public class MongoClientSessionEntity extends AbstractIdentifiableEntity impleme
         this.sessionId = sessionId;
     }
 
-    public Map<String, UserSessionModel.AuthenticatorStatus> getAuthenticatorStatus() {
+    public Map<String, ClientSessionModel.ExecutionStatus> getAuthenticatorStatus() {
         return authenticatorStatus;
     }
 
-    public void setAuthenticatorStatus(Map<String, UserSessionModel.AuthenticatorStatus> authenticatorStatus) {
+    public void setAuthenticatorStatus(Map<String, ClientSessionModel.ExecutionStatus> authenticatorStatus) {
         this.authenticatorStatus = authenticatorStatus;
     }
 
diff --git a/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java b/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java
index bbafc76..fce95a2 100755
--- a/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java
+++ b/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java
@@ -3,6 +3,7 @@ package org.keycloak.authentication;
 import org.jboss.logging.Logger;
 import org.jboss.resteasy.spi.HttpRequest;
 import org.keycloak.ClientConnection;
+import org.keycloak.authentication.authenticators.AbstractFormAuthenticator;
 import org.keycloak.events.Details;
 import org.keycloak.events.Errors;
 import org.keycloak.events.EventBuilder;
@@ -17,23 +18,23 @@ import org.keycloak.models.UserModel;
 import org.keycloak.models.UserSessionModel;
 import org.keycloak.protocol.oidc.TokenManager;
 import org.keycloak.services.ErrorPage;
-import org.keycloak.services.managers.Auth;
 import org.keycloak.services.managers.AuthenticationManager;
 import org.keycloak.services.managers.BruteForceProtector;
 import org.keycloak.services.managers.ClientSessionCode;
 import org.keycloak.services.messages.Messages;
+import org.keycloak.util.Time;
 
 import javax.ws.rs.core.Response;
 import javax.ws.rs.core.UriInfo;
-import java.util.Collections;
+import java.util.HashMap;
 import java.util.Iterator;
-import java.util.List;
 
 /**
  * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
  * @version $Revision: 1 $
  */
 public class AuthenticationProcessor {
+    public static final String CURRENT_AUTHENTICATION_EXECUTION = "current.authentication.execution";
     protected static Logger logger = Logger.getLogger(AuthenticationProcessor.class);
     protected RealmModel realm;
     protected UserSessionModel userSession;
@@ -325,7 +326,7 @@ public class AuthenticationProcessor {
         @Override
         public String generateAccessCode() {
             ClientSessionCode accessCode = new ClientSessionCode(getRealm(), getClientSession());
-            accessCode.setAction(ClientSessionModel.Action.AUTHENTICATE.name());
+            clientSession.setTimestamp(Time.currentTime());
             return accessCode.getCode();
         }
     }
@@ -362,23 +363,32 @@ public class AuthenticationProcessor {
         }
     }
 
-    public void logUserFailure() {
+    public void logFailure() {
+        if (realm.isBruteForceProtected()) {
+            String username = clientSession.getNote(AbstractFormAuthenticator.ATTEMPTED_USERNAME);
+            // todo need to handle non form failures
+            if (username == null) {
+
+            } else {
+                protector.failedLogin(realm, username, connection);
 
+            }
+        }
     }
 
     protected boolean isProcessed(AuthenticationExecutionModel model) {
         if (model.isDisabled()) return true;
-        UserSessionModel.AuthenticatorStatus status = clientSession.getAuthenticators().get(model.getId());
+        ClientSessionModel.ExecutionStatus status = clientSession.getExecutionStatus().get(model.getId());
         if (status == null) return false;
-        return status == UserSessionModel.AuthenticatorStatus.SUCCESS || status == UserSessionModel.AuthenticatorStatus.SKIPPED
-                || status == UserSessionModel.AuthenticatorStatus.ATTEMPTED
-                || status == UserSessionModel.AuthenticatorStatus.SETUP_REQUIRED;
+        return status == ClientSessionModel.ExecutionStatus.SUCCESS || status == ClientSessionModel.ExecutionStatus.SKIPPED
+                || status == ClientSessionModel.ExecutionStatus.ATTEMPTED
+                || status == ClientSessionModel.ExecutionStatus.SETUP_REQUIRED;
     }
 
     public boolean isSuccessful(AuthenticationExecutionModel model) {
-        UserSessionModel.AuthenticatorStatus status = clientSession.getAuthenticators().get(model.getId());
+        ClientSessionModel.ExecutionStatus status = clientSession.getExecutionStatus().get(model.getId());
         if (status == null) return false;
-        return status == UserSessionModel.AuthenticatorStatus.SUCCESS;
+        return status == ClientSessionModel.ExecutionStatus.SUCCESS;
     }
 
     public Response handleBrowserException(Exception failure) {
@@ -449,6 +459,51 @@ public class AuthenticationProcessor {
         return authenticationComplete();
     }
 
+    protected void resetFlow() {
+        clientSession.clearExecutionStatus();
+        clientSession.clearUserSessionNotes();
+        clientSession.removeNote(CURRENT_AUTHENTICATION_EXECUTION);
+    }
+
+    public Response authenticationAction(String execution) {
+        checkClientSession();
+        String current = clientSession.getNote(CURRENT_AUTHENTICATION_EXECUTION);
+        if (!execution.equals(current)) {
+            logger.debug("Current execution does not equal executed execution.  Might be a page refresh");
+            logFailure();
+            resetFlow();
+            return authenticate();
+        }
+        AuthenticationExecutionModel model = realm.getAuthenticationExecutionById(execution);
+        if (model == null) {
+            logger.debug("Cannot find execution, reseting flow");
+            logFailure();
+            resetFlow();
+            return authenticate();
+        }
+        event.event(EventType.LOGIN);
+        event.client(clientSession.getClient().getClientId())
+                .detail(Details.REDIRECT_URI, clientSession.getRedirectUri())
+                .detail(Details.AUTH_METHOD, clientSession.getAuthMethod());
+        String authType = clientSession.getNote(Details.AUTH_TYPE);
+        if (authType != null) {
+            event.detail(Details.AUTH_TYPE, authType);
+        }
+        AuthenticatorModel authenticatorModel = realm.getAuthenticatorById(model.getAuthenticator());
+        AuthenticatorFactory factory = (AuthenticatorFactory)session.getKeycloakSessionFactory().getProviderFactory(Authenticator.class, authenticatorModel.getProviderId());
+        Authenticator authenticator = factory.create(authenticatorModel);
+        Result context = new Result(model, authenticatorModel, authenticator);
+        authenticator.action(context);
+
+        FlowExecution flowExecution = createFlowExecution(this.flowId);
+        Response challenge = flowExecution.action(execution, context);
+        if (challenge != null) return challenge;
+        if (clientSession.getAuthenticatedUser() == null) {
+            throw new AuthException(Error.UNKNOWN_USER);
+        }
+        return authenticationComplete();
+    }
+
     public void checkClientSession() {
         ClientSessionCode code = new ClientSessionCode(realm, clientSession);
         if (!code.isValidAction(ClientSessionModel.Action.AUTHENTICATE.name())) {
@@ -457,6 +512,7 @@ public class AuthenticationProcessor {
         if (!code.isActionActive(ClientSessionModel.Action.AUTHENTICATE.name())) {
             throw new AuthException(Error.EXPIRED_CODE);
         }
+        clientSession.setTimestamp(Time.currentTime());
     }
 
     public Response authenticateOnly() throws AuthException {
@@ -566,14 +622,14 @@ public class AuthenticationProcessor {
                     continue;
                 }
                 if (model.isAlternative() && alternativeSuccessful) {
-                    clientSession.setAuthenticatorStatus(model.getId(), UserSessionModel.AuthenticatorStatus.SKIPPED);
+                    clientSession.setExecutionStatus(model.getId(), ClientSessionModel.ExecutionStatus.SKIPPED);
                     continue;
                 }
                 if (model.isAutheticatorFlow()) {
                     FlowExecution flowExecution = createFlowExecution(model.getAuthenticator());
                     Response flowResponse = flowExecution.processFlow();
                     if (flowResponse == null) {
-                        clientSession.setAuthenticatorStatus(model.getId(), UserSessionModel.AuthenticatorStatus.SUCCESS);
+                        clientSession.setExecutionStatus(model.getId(), ClientSessionModel.ExecutionStatus.SUCCESS);
                         if (model.isAlternative()) alternativeSuccessful = true;
                         continue;
                     } else {
@@ -590,7 +646,7 @@ public class AuthenticationProcessor {
 
                 if (authenticator.requiresUser() && authUser == null){
                     if (alternativeChallenge != null) {
-                        clientSession.setAuthenticatorStatus(challengedAlternativeExecution.getId(), UserSessionModel.AuthenticatorStatus.CHALLENGED);
+                        clientSession.setExecutionStatus(challengedAlternativeExecution.getId(), ClientSessionModel.ExecutionStatus.CHALLENGED);
                         return alternativeChallenge;
                     }
                     throw new AuthException("authenticator: " + authenticatorModel.getProviderId(), Error.UNKNOWN_USER);
@@ -602,14 +658,14 @@ public class AuthenticationProcessor {
                         if (model.isRequired()) {
                             if (model.isUserSetupAllowed()) {
                                 logger.debugv("authenticator SETUP_REQUIRED: {0}", authenticatorModel.getProviderId());
-                                clientSession.setAuthenticatorStatus(model.getId(), UserSessionModel.AuthenticatorStatus.SETUP_REQUIRED);
+                                clientSession.setExecutionStatus(model.getId(), ClientSessionModel.ExecutionStatus.SETUP_REQUIRED);
                                 authenticator.setRequiredActions(session, realm, clientSession.getAuthenticatedUser());
                                 continue;
                             } else {
                                 throw new AuthException(Error.CREDENTIAL_SETUP_REQUIRED);
                             }
                         } else if (model.isOptional()) {
-                            clientSession.setAuthenticatorStatus(model.getId(), UserSessionModel.AuthenticatorStatus.SKIPPED);
+                            clientSession.setExecutionStatus(model.getId(), ClientSessionModel.ExecutionStatus.SKIPPED);
                             continue;
                         }
                     }
@@ -629,47 +685,49 @@ public class AuthenticationProcessor {
             Status status = result.getStatus();
             if (status == Status.SUCCESS){
                 logger.debugv("authenticator SUCCESS: {0}", authenticatorModel.getProviderId());
-                clientSession.setAuthenticatorStatus(execution.getId(), UserSessionModel.AuthenticatorStatus.SUCCESS);
+                clientSession.setExecutionStatus(execution.getId(), ClientSessionModel.ExecutionStatus.SUCCESS);
                 if (execution.isAlternative()) alternativeSuccessful = true;
                 return null;
             } else if (status == Status.FAILED) {
                 logger.debugv("authenticator FAILED: {0}", authenticatorModel.getProviderId());
-                logUserFailure();
-                clientSession.setAuthenticatorStatus(execution.getId(), UserSessionModel.AuthenticatorStatus.FAILED);
-                if (result.challenge != null) return result.challenge;
+                logFailure();
+                clientSession.setExecutionStatus(execution.getId(), ClientSessionModel.ExecutionStatus.FAILED);
+                if (result.challenge != null) {
+                    return sendChallenge(result, execution);
+                }
                 throw new AuthException(result.error);
             } else if (status == Status.FORCE_CHALLENGE) {
-                clientSession.setAuthenticatorStatus(execution.getId(), UserSessionModel.AuthenticatorStatus.CHALLENGED);
-                return result.challenge;
+                clientSession.setExecutionStatus(execution.getId(), ClientSessionModel.ExecutionStatus.CHALLENGED);
+                return sendChallenge(result, execution);
             } else if (status == Status.CHALLENGE) {
                 logger.debugv("authenticator CHALLENGE: {0}", authenticatorModel.getProviderId());
                 if (execution.isRequired()) {
-                    clientSession.setAuthenticatorStatus(execution.getId(), UserSessionModel.AuthenticatorStatus.CHALLENGED);
-                    return result.challenge;
+                    clientSession.setExecutionStatus(execution.getId(), ClientSessionModel.ExecutionStatus.CHALLENGED);
+                    return sendChallenge(result, execution);
                 }
                 UserModel authenticatedUser = clientSession.getAuthenticatedUser();
                 if (execution.isOptional() && authenticatedUser != null && result.getAuthenticator().configuredFor(session, realm, authenticatedUser)) {
-                    clientSession.setAuthenticatorStatus(execution.getId(), UserSessionModel.AuthenticatorStatus.CHALLENGED);
-                    return result.challenge;
+                    clientSession.setExecutionStatus(execution.getId(), ClientSessionModel.ExecutionStatus.CHALLENGED);
+                    return sendChallenge(result, execution);
                 }
                 if (execution.isAlternative()) {
                     alternativeChallenge = result.challenge;
                     challengedAlternativeExecution = execution;
                 } else {
-                    clientSession.setAuthenticatorStatus(execution.getId(), UserSessionModel.AuthenticatorStatus.SKIPPED);
+                    clientSession.setExecutionStatus(execution.getId(), ClientSessionModel.ExecutionStatus.SKIPPED);
                 }
                 return null;
             } else if (status == Status.FAILURE_CHALLENGE) {
                 logger.debugv("authenticator FAILURE_CHALLENGE: {0}", authenticatorModel.getProviderId());
-                logUserFailure();
-                clientSession.setAuthenticatorStatus(execution.getId(), UserSessionModel.AuthenticatorStatus.CHALLENGED);
-                return result.challenge;
+                logFailure();
+                clientSession.setExecutionStatus(execution.getId(), ClientSessionModel.ExecutionStatus.CHALLENGED);
+                return sendChallenge(result, execution);
             } else if (status == Status.ATTEMPTED) {
                 logger.debugv("authenticator ATTEMPTED: {0}", authenticatorModel.getProviderId());
                 if (execution.getRequirement() == AuthenticationExecutionModel.Requirement.REQUIRED) {
                     throw new AuthException(Error.INVALID_CREDENTIALS);
                 }
-                clientSession.setAuthenticatorStatus(execution.getId(), UserSessionModel.AuthenticatorStatus.ATTEMPTED);
+                clientSession.setExecutionStatus(execution.getId(), ClientSessionModel.ExecutionStatus.ATTEMPTED);
                 return null;
             } else {
                 logger.debugv("authenticator INTERNAL_ERROR: {0}", authenticatorModel.getProviderId());
@@ -679,8 +737,13 @@ public class AuthenticationProcessor {
 
         }
 
+         public Response sendChallenge(Result result, AuthenticationExecutionModel execution) {
+             clientSession.setNote(CURRENT_AUTHENTICATION_EXECUTION, execution.getId());
+             return result.challenge;
+         }
 
-    }
+
+     }
 
 
 
diff --git a/services/src/main/java/org/keycloak/authentication/Authenticator.java b/services/src/main/java/org/keycloak/authentication/Authenticator.java
index 8245f82..36e6d52 100755
--- a/services/src/main/java/org/keycloak/authentication/Authenticator.java
+++ b/services/src/main/java/org/keycloak/authentication/Authenticator.java
@@ -20,5 +20,7 @@ public interface Authenticator extends Provider {
      */
     void setRequiredActions(KeycloakSession session, RealmModel realm, UserModel user);
 
+    void action(AuthenticatorContext context);
+
 
 }
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/AbstractFormAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/AbstractFormAuthenticator.java
index 6c611b1..957c540 100755
--- a/services/src/main/java/org/keycloak/authentication/authenticators/AbstractFormAuthenticator.java
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/AbstractFormAuthenticator.java
@@ -2,6 +2,7 @@ package org.keycloak.authentication.authenticators;
 
 import org.keycloak.OAuth2Constants;
 import org.keycloak.authentication.AuthenticationProcessor;
+import org.keycloak.authentication.Authenticator;
 import org.keycloak.authentication.AuthenticatorContext;
 import org.keycloak.events.Details;
 import org.keycloak.events.Errors;
@@ -24,20 +25,25 @@ import java.util.List;
  * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
  * @version $Revision: 1 $
  */
-public class AbstractFormAuthenticator {
+public abstract class AbstractFormAuthenticator implements Authenticator {
 
-    public static final String LOGIN_FORM_ACTION = "login_form";
     public static final String REGISTRATION_FORM_ACTION = "registration_form";
-    public static final String ACTION = "action";
-    public static final String FORM_USERNAME = "FORM_USERNAME";
+    public static final String EXECUTION = "execution";
+    public static final String ATTEMPTED_USERNAME = "ATTEMPTED_USERNAME";
+
+    @Override
+    public void action(AuthenticatorContext context) {
+
+    }
+
+    @Override
+    public void close() {
 
-    protected boolean isAction(AuthenticatorContext context, String action) {
-        return action.equals(context.getAction());
     }
 
     protected LoginFormsProvider loginForm(AuthenticatorContext context) {
         String accessCode = context.generateAccessCode();
-        URI action = getActionUrl(context, accessCode, LOGIN_FORM_ACTION);
+        URI action = getActionUrl(context, accessCode);
         LoginFormsProvider provider = context.getSession().getProvider(LoginFormsProvider.class)
                     .setUser(context.getUser())
                     .setActionUri(action)
@@ -48,10 +54,10 @@ public class AbstractFormAuthenticator {
         return provider;
     }
 
-    public static URI getActionUrl(AuthenticatorContext context, String code, String action) {
+    public URI getActionUrl(AuthenticatorContext context, String code) {
         return LoginActionsService.authenticationFormProcessor(context.getUriInfo())
                 .queryParam(OAuth2Constants.CODE, code)
-                .queryParam(ACTION, action)
+                .queryParam(EXECUTION, context.getExecution().getId())
                     .build(context.getRealm().getName());
     }
 
@@ -111,7 +117,7 @@ public class AbstractFormAuthenticator {
             return false;
         }
         context.getEvent().detail(Details.USERNAME, username);
-        context.getClientSession().setNote(AbstractFormAuthenticator.FORM_USERNAME, username);
+        context.getClientSession().setNote(AbstractFormAuthenticator.ATTEMPTED_USERNAME, username);
         UserModel user = KeycloakModelUtils.findUserByNameOrEmail(context.getSession(), context.getRealm(), username);
         if (invalidUser(context, user)) return false;
         String rememberMe = inputData.getFirst("rememberMe");
@@ -119,6 +125,8 @@ public class AbstractFormAuthenticator {
         if (remember) {
             context.getClientSession().setNote(Details.REMEMBER_ME, "true");
             context.getEvent().detail(Details.REMEMBER_ME, "true");
+        } else {
+            context.getClientSession().removeNote(Details.REMEMBER_ME);
         }
         context.setUser(user);
         return true;
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/CookieAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/CookieAuthenticator.java
index a4d6430..7e68a02 100755
--- a/services/src/main/java/org/keycloak/authentication/authenticators/CookieAuthenticator.java
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/CookieAuthenticator.java
@@ -33,6 +33,11 @@ public class CookieAuthenticator implements Authenticator {
     }
 
     @Override
+    public void action(AuthenticatorContext context) {
+
+    }
+
+    @Override
     public boolean configuredFor(KeycloakSession session, RealmModel realm, UserModel user) {
         return true;
     }
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/OTPFormAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/OTPFormAuthenticator.java
index bce060b..fd2aa08 100755
--- a/services/src/main/java/org/keycloak/authentication/authenticators/OTPFormAuthenticator.java
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/OTPFormAuthenticator.java
@@ -32,15 +32,16 @@ public class OTPFormAuthenticator extends AbstractFormAuthenticator implements A
     }
 
     @Override
-    public void authenticate(AuthenticatorContext context) {
-        if (!isAction(context, TOTP_FORM_ACTION)) {
-            Response challengeResponse = challenge(context, null);
-            context.challenge(challengeResponse);
-            return;
-        }
+    public void action(AuthenticatorContext context) {
         validateOTP(context);
     }
 
+    @Override
+    public void authenticate(AuthenticatorContext context) {
+        Response challengeResponse = challenge(context, null);
+        context.challenge(challengeResponse);
+    }
+
     public void validateOTP(AuthenticatorContext context) {
         MultivaluedMap<String, String> inputData = context.getHttpRequest().getDecodedFormParameters();
         List<UserCredentialModel> credentials = new LinkedList<>();
@@ -69,7 +70,7 @@ public class OTPFormAuthenticator extends AbstractFormAuthenticator implements A
 
     protected Response challenge(AuthenticatorContext context, String error) {
         String accessCode = context.generateAccessCode();
-        URI action = AbstractFormAuthenticator.getActionUrl(context, accessCode, TOTP_FORM_ACTION);
+        URI action = getActionUrl(context, accessCode);
         LoginFormsProvider forms = context.getSession().getProvider(LoginFormsProvider.class)
                 .setActionUri(action)
                 .setClientSessionCode(accessCode);
@@ -91,6 +92,8 @@ public class OTPFormAuthenticator extends AbstractFormAuthenticator implements A
 
     }
 
+
+
     @Override
     public void close() {
 
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/SpnegoAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/SpnegoAuthenticator.java
index eb6c1ee..0696e06 100755
--- a/services/src/main/java/org/keycloak/authentication/authenticators/SpnegoAuthenticator.java
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/SpnegoAuthenticator.java
@@ -39,26 +39,17 @@ public class SpnegoAuthenticator extends AbstractFormAuthenticator implements Au
         return false;
     }
 
-    protected boolean isAlreadyChallenged(AuthenticatorContext context) {
-        UserSessionModel.AuthenticatorStatus status = context.getClientSession().getAuthenticators().get(context.getExecution().getId());
-        if (status == null) return false;
-        return status == UserSessionModel.AuthenticatorStatus.CHALLENGED;
+    @Override
+    public void action(AuthenticatorContext context) {
+        context.attempted();
+        return;
     }
 
     @Override
     public void authenticate(AuthenticatorContext context) {
         HttpRequest request = context.getHttpRequest();
         String authHeader = request.getHttpHeaders().getRequestHeaders().getFirst(HttpHeaders.AUTHORIZATION);
-        if (isAction(context, KERBEROS_DISABLED)) {
-            context.attempted();
-            return;
-        }
-        // Case when we don't yet have any Negotiate header
         if (authHeader == null) {
-            if (isAlreadyChallenged(context)) {
-                context.attempted();
-                return;
-            }
             Response challenge = challengeNegotiation(context, null);
             context.forceChallenge(challenge);
             return;
@@ -131,7 +122,7 @@ public class SpnegoAuthenticator extends AbstractFormAuthenticator implements Au
      */
     protected Response optionalChallengeRedirect(AuthenticatorContext context, String negotiateHeader) {
         String accessCode = context.generateAccessCode();
-        URI action = getActionUrl(context, accessCode, KERBEROS_DISABLED);
+        URI action = getActionUrl(context, accessCode);
 
         StringBuilder builder = new StringBuilder();
 
@@ -159,18 +150,6 @@ public class SpnegoAuthenticator extends AbstractFormAuthenticator implements Au
                 .entity(builder.toString()).build();
     }
 
-    protected Response formChallenge(AuthenticatorContext context, String negotiateHeader) {
-        String accessCode = context.generateAccessCode();
-        URI action = getActionUrl(context, accessCode, KERBEROS_DISABLED);
-        return context.getSession().getProvider(LoginFormsProvider.class)
-                .setClientSessionCode(accessCode)
-                .setActionUri(action)
-                .setStatus(Response.Status.UNAUTHORIZED)
-                .setResponseHeader(HttpHeaders.WWW_AUTHENTICATE, negotiateHeader)
-                .setUser(context.getUser())
-                .createForm("bypass_kerberos.ftl", new HashMap<String, Object>());
-    }
-
 
     @Override
     public boolean configuredFor(KeycloakSession session, RealmModel realm, UserModel user) {
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/UsernamePasswordForm.java b/services/src/main/java/org/keycloak/authentication/authenticators/UsernamePasswordForm.java
index 3dd81d7..2c25bac 100755
--- a/services/src/main/java/org/keycloak/authentication/authenticators/UsernamePasswordForm.java
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/UsernamePasswordForm.java
@@ -4,24 +4,18 @@ import org.jboss.resteasy.specimpl.MultivaluedMapImpl;
 import org.keycloak.authentication.AuthenticationProcessor;
 import org.keycloak.authentication.Authenticator;
 import org.keycloak.authentication.AuthenticatorContext;
-import org.keycloak.events.Details;
 import org.keycloak.events.Errors;
 import org.keycloak.login.LoginFormsProvider;
 import org.keycloak.models.AuthenticatorModel;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.RealmModel;
-import org.keycloak.models.UserCredentialModel;
 import org.keycloak.models.UserModel;
-import org.keycloak.models.utils.KeycloakModelUtils;
 import org.keycloak.protocol.LoginProtocol;
 import org.keycloak.protocol.oidc.OIDCLoginProtocol;
-import org.keycloak.representations.idm.CredentialRepresentation;
 import org.keycloak.services.managers.AuthenticationManager;
 
 import javax.ws.rs.core.MultivaluedMap;
 import javax.ws.rs.core.Response;
-import java.util.LinkedList;
-import java.util.List;
 
 /**
  * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@@ -34,30 +28,8 @@ public class UsernamePasswordForm extends AbstractFormAuthenticator implements A
         this.model = model;
     }
 
-    @Override
-    public void authenticate(AuthenticatorContext context) {
-        if (isAction(context, REGISTRATION_FORM_ACTION) && context.getUser() != null) {
-            context.success();
-            return;
-        }
-        if (!isAction(context, LOGIN_FORM_ACTION)) {
-            MultivaluedMap<String, String> formData = new MultivaluedMapImpl<>();
-            String loginHint = context.getClientSession().getNote(OIDCLoginProtocol.LOGIN_HINT_PARAM);
-
-            String rememberMeUsername = AuthenticationManager.getRememberMeUsername(context.getRealm(), context.getHttpRequest().getHttpHeaders());
-
-            if (loginHint != null || rememberMeUsername != null) {
-                if (loginHint != null) {
-                    formData.add(AuthenticationManager.FORM_USERNAME, loginHint);
-                } else {
-                    formData.add(AuthenticationManager.FORM_USERNAME, rememberMeUsername);
-                    formData.add("rememberMe", "on");
-                }
-            }
-            Response challengeResponse = challenge(context, formData);
-            context.challenge(challengeResponse);
-            return;
-        }
+   @Override
+    public void action(AuthenticatorContext context) {
         MultivaluedMap<String, String> formData = context.getHttpRequest().getDecodedFormParameters();
         if (formData.containsKey("cancel")) {
             context.getEvent().error(Errors.REJECTED_BY_USER);
@@ -66,10 +38,9 @@ public class UsernamePasswordForm extends AbstractFormAuthenticator implements A
                     .setHttpHeaders(context.getHttpRequest().getHttpHeaders())
                     .setUriInfo(context.getUriInfo());
             Response response = protocol.cancelLogin(context.getClientSession());
-            context.challenge(response);
+            context.forceChallenge(response);
             return;
         }
-
         if (!validateUser(context, formData)) {
             return;
         }
@@ -77,7 +48,30 @@ public class UsernamePasswordForm extends AbstractFormAuthenticator implements A
             return;
         }
         context.success();
+    }
 
+    @Override
+    public void authenticate(AuthenticatorContext context) {
+        if (REGISTRATION_FORM_ACTION.equals(context.getAction()) && context.getUser() != null) {
+            context.success();
+            return;
+        }
+        MultivaluedMap<String, String> formData = new MultivaluedMapImpl<>();
+        String loginHint = context.getClientSession().getNote(OIDCLoginProtocol.LOGIN_HINT_PARAM);
+
+        String rememberMeUsername = AuthenticationManager.getRememberMeUsername(context.getRealm(), context.getHttpRequest().getHttpHeaders());
+
+        if (loginHint != null || rememberMeUsername != null) {
+            if (loginHint != null) {
+                formData.add(AuthenticationManager.FORM_USERNAME, loginHint);
+            } else {
+                formData.add(AuthenticationManager.FORM_USERNAME, rememberMeUsername);
+                formData.add("rememberMe", "on");
+            }
+        }
+        Response challengeResponse = challenge(context, formData);
+        context.getClientSession().setNote(AuthenticationProcessor.CURRENT_AUTHENTICATION_EXECUTION, context.getExecution().getId());
+        context.challenge(challengeResponse);
     }
 
     @Override
diff --git a/services/src/main/java/org/keycloak/services/managers/ClientSessionCode.java b/services/src/main/java/org/keycloak/services/managers/ClientSessionCode.java
index d203218..b19295b 100755
--- a/services/src/main/java/org/keycloak/services/managers/ClientSessionCode.java
+++ b/services/src/main/java/org/keycloak/services/managers/ClientSessionCode.java
@@ -142,25 +142,6 @@ public class ClientSessionCode {
         clientSession.setTimestamp(Time.currentTime());
     }
 
-    public void setRequiredAction(RequiredAction requiredAction) {
-        setAction(convertToAction(requiredAction));
-    }
-
-    private String convertToAction(RequiredAction requiredAction) {
-        switch (requiredAction) {
-            case CONFIGURE_TOTP:
-                return ClientSessionModel.Action.CONFIGURE_TOTP.name();
-            case UPDATE_PASSWORD:
-                return ClientSessionModel.Action.UPDATE_PASSWORD.name();
-            case UPDATE_PROFILE:
-                return ClientSessionModel.Action.UPDATE_PROFILE.name();
-            case VERIFY_EMAIL:
-                return ClientSessionModel.Action.VERIFY_EMAIL.name();
-            default:
-                throw new IllegalArgumentException("Unknown required action " + requiredAction);
-        }
-    }
-
     public String getCode() {
         return generateCode(realm, clientSession);
     }
diff --git a/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java b/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java
index 3bec193..cca6bb0 100755
--- a/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java
+++ b/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java
@@ -23,6 +23,7 @@ package org.keycloak.services.resources;
 
 import org.jboss.logging.Logger;
 import org.jboss.resteasy.spi.HttpRequest;
+import org.jboss.resteasy.spi.InternalServerErrorException;
 import org.keycloak.ClientConnection;
 import org.keycloak.authentication.AuthenticationProcessor;
 import org.keycloak.authentication.AuthenticatorUtil;
@@ -65,6 +66,7 @@ import org.keycloak.services.ErrorPage;
 import org.keycloak.services.Urls;
 import org.keycloak.services.util.CookieHelper;
 import org.keycloak.services.validation.Validation;
+import org.keycloak.util.Time;
 
 import javax.ws.rs.Consumes;
 import javax.ws.rs.GET;
@@ -234,7 +236,7 @@ public class LoginActionsService {
     @Path("authenticate")
     @GET
     public Response authenticate(@QueryParam("code") String code,
-                                 @QueryParam("action") String action) {
+                                 @QueryParam("execution") String execution) {
         event.event(EventType.LOGIN);
         Checks checks = new Checks();
         if (!checks.check(code)) {
@@ -249,28 +251,58 @@ public class LoginActionsService {
             clientSession.setAction(ClientSessionModel.Action.AUTHENTICATE.name());
         }
 
-        AuthenticationFlowModel flow = realm.getFlowByAlias(DefaultAuthenticationFlows.BROWSER_FLOW);
-        String flowId = flow.getId();
+        return processAuthentication(execution, clientSession);
+    }
+
+    protected Response processAuthentication(String execution, ClientSessionModel clientSession) {
+        String flowAlias = DefaultAuthenticationFlows.BROWSER_FLOW;
+        AuthenticationFlowModel flow = realm.getFlowByAlias(flowAlias);
         AuthenticationProcessor processor = new AuthenticationProcessor();
         processor.setClientSession(clientSession)
-                .setFlowId(flowId)
+                .setFlowId(flow.getId())
                 .setConnection(clientConnection)
                 .setEventBuilder(event)
                 .setProtector(authManager.getProtector())
-                .setAction(action)
                 .setRealm(realm)
                 .setSession(session)
                 .setUriInfo(uriInfo)
                 .setRequest(request);
 
         try {
-            return processor.authenticate();
+            if (execution != null) {
+                return processor.authenticationAction(execution);
+            } else {
+                return processor.authenticate();
+            }
         } catch (Exception e) {
             return processor.handleBrowserException(e);
         }
     }
 
     /**
+     * URL called after login page.  YOU SHOULD NEVER INVOKE THIS DIRECTLY!
+     *
+     * @param code
+     * @return
+     */
+    @Path("authenticate")
+    @POST
+    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
+    public Response authenticateForm(@QueryParam("code") String code,
+                                     @QueryParam("execution") String execution) {
+        event.event(EventType.LOGIN);
+        Checks checks = new Checks();
+        if (!checks.check(code, ClientSessionModel.Action.AUTHENTICATE.name())) {
+            return checks.response;
+        }
+        final ClientSessionCode clientCode = checks.clientCode;
+        final ClientSessionModel clientSession = clientCode.getClientSession();
+
+        return processAuthentication(execution, clientSession);
+    }
+
+
+    /**
      * protocol independent registration page entry point
      *
      * @param code
@@ -302,46 +334,6 @@ public class LoginActionsService {
                 .createRegistration();
     }
 
-    /**
-     * URL called after login page.  YOU SHOULD NEVER INVOKE THIS DIRECTLY!
-     *
-     * @param code
-     * @return
-     */
-    @Path("authenticate")
-    @POST
-    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
-    public Response authenticateForm(@QueryParam("code") String code,
-                             @QueryParam("action") String action) {
-        event.event(EventType.LOGIN);
-        Checks checks = new Checks();
-        if (!checks.check(code, ClientSessionModel.Action.AUTHENTICATE.name())) {
-            return checks.response;
-        }
-        final ClientSessionCode clientCode = checks.clientCode;
-        final ClientSessionModel clientSession = clientCode.getClientSession();
-
-        String flowAlias = DefaultAuthenticationFlows.BROWSER_FLOW;
-        AuthenticationFlowModel flow = realm.getFlowByAlias(flowAlias);
-        AuthenticationProcessor processor = new AuthenticationProcessor();
-        processor.setClientSession(clientSession)
-                .setFlowId(flow.getId())
-                .setConnection(clientConnection)
-                .setEventBuilder(event)
-                .setProtector(authManager.getProtector())
-                .setRealm(realm)
-                .setSession(session)
-                .setUriInfo(uriInfo)
-                .setAction(action)
-                .setRequest(request);
-
-        try {
-            return processor.authenticate();
-        } catch (Exception e) {
-            return processor.handleBrowserException(e);
-        }
-
-    }
 
     /**
      * Registration
@@ -946,7 +938,7 @@ public class LoginActionsService {
     }
 
     @Path("required-actions/{action}")
-    public Object requiredAction(@QueryParam("code") String code,
+    public Object requiredAction(@QueryParam("code") final String code,
                                  @PathParam("action") String action) {
         event.event(EventType.LOGIN);
         if (action == null) {
@@ -1024,6 +1016,11 @@ public class LoginActionsService {
 
             @Override
             public String generateAccessCode(String action) {
+                String clientSessionAction = clientSession.getAction();
+                if (action.equals(clientSessionAction)) {
+                    clientSession.setTimestamp(Time.currentTime());
+                    return code;
+                }
                 ClientSessionCode code = new ClientSessionCode(getRealm(), getClientSession());
                 code.setAction(action);
                 return code.getCode();