keycloak-aplcache

1st phase auth/required action spi

5/18/2015 5:32:38 PM

Changes

Details

diff --git a/connections/jpa/src/main/java/org/keycloak/connections/jpa/updater/JpaUpdaterProvider.java b/connections/jpa/src/main/java/org/keycloak/connections/jpa/updater/JpaUpdaterProvider.java
index 2b94243..ad41016 100755
--- a/connections/jpa/src/main/java/org/keycloak/connections/jpa/updater/JpaUpdaterProvider.java
+++ b/connections/jpa/src/main/java/org/keycloak/connections/jpa/updater/JpaUpdaterProvider.java
@@ -12,7 +12,7 @@ public interface JpaUpdaterProvider extends Provider {
 
     public String FIRST_VERSION = "1.0.0.Final";
 
-    public String LAST_VERSION = "1.2.0.RC1";
+    public String LAST_VERSION = "1.3.0.Beta1";
 
     public String getCurrentVersionSql();
 
diff --git a/connections/jpa/src/main/resources/META-INF/persistence.xml b/connections/jpa/src/main/resources/META-INF/persistence.xml
index 4dbad93..55ff7f0 100755
--- a/connections/jpa/src/main/resources/META-INF/persistence.xml
+++ b/connections/jpa/src/main/resources/META-INF/persistence.xml
@@ -28,6 +28,7 @@
         <!-- JpaUserSessionProvider -->
         <class>org.keycloak.models.sessions.jpa.entities.ClientSessionEntity</class>
         <class>org.keycloak.models.sessions.jpa.entities.ClientSessionRoleEntity</class>
+        <class>org.keycloak.models.sessions.jpa.entities.ClientSessionAuthStatusEntity</class>
         <class>org.keycloak.models.sessions.jpa.entities.ClientSessionProtocolMapperEntity</class>
         <class>org.keycloak.models.sessions.jpa.entities.ClientSessionNoteEntity</class>
         <class>org.keycloak.models.sessions.jpa.entities.UserSessionNoteEntity</class>
diff --git a/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.3.0.Beta1.xml b/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.3.0.Beta1.xml
new file mode 100755
index 0000000..50e8a48
--- /dev/null
+++ b/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.3.0.Beta1.xml
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.1.xsd">
+    <changeSet author="bburke@redhat.com" id="1.3.0.Beta1">
+        <delete tableName="CLIENT_SESSION_ROLE"/>
+        <delete tableName="CLIENT_SESSION_NOTE"/>
+        <delete tableName="CLIENT_SESSION"/>
+        <delete tableName="USER_SESSION_NOTE"/>
+        <delete tableName="USER_SESSION"/>
+        <createTable tableName="CLIENT_SESSION_AUTH_STATUS">
+            <column name="AUTHENTICATOR" type="VARCHAR(32)">
+                <constraints nullable="false"/>
+            </column>
+            <column name="STATUS" type="INT"/>
+            <column name="CLIENT_SESSION" type="VARCHAR(36)">
+                <constraints nullable="false"/>
+            </column>
+        </createTable>
+        <addColumn tableName="CLIENT_SESSION">
+            <column name="AUTH_USER_ID" type="VARCHAR(32)"/>
+        </addColumn>
+        <addColumn tableName="USER_REQUIRED_ACTION">
+            <column name="REQUIRED_ACTION" type="VARCHAR(32)">
+                <constraints nullable="false"/>
+            </column>
+        </addColumn>
+        <!-- VERIFY_EMAIL, UPDATE_PROFILE, CONFIGURE_TOTP, UPDATE_PASSWORD -->
+        <update tableName="USER_REQUIRED_ACTION">
+            <column name="REQUIRED_ACTION" value="VERIFY_EMAIL"/>
+            <where>ACTION = 0</where>
+        </update>
+        <update tableName="USER_REQUIRED_ACTION">
+            <column name="REQUIRED_ACTION" value="UPDATE_PROFILE"/>
+            <where>ACTION = 1</where>
+        </update>
+        <update tableName="USER_REQUIRED_ACTION">
+            <column name="REQUIRED_ACTION" value="CONFIGURE_TOTP"/>
+            <where>ACTION = 2</where>
+        </update>
+        <update tableName="USER_REQUIRED_ACTION">
+            <column name="REQUIRED_ACTION" value="UPDATE_PASSWORD"/>
+            <where>ACTION = 3</where>
+        </update>
+        <dropPrimaryKey constraintName="CONSTRAINT_2" tableName="USER_REQUIRED_ACTION"/>
+        <dropColumn tableName="USER_REQUIRED_ACTION" columnName="ACTION"/>
+        <addPrimaryKey columnNames="REQUIRED_ACTION, USER_ID" constraintName="CONSTRAINT_REQUIRED_ACTION" tableName="USER_REQUIRED_ACTION"/>
+        <addPrimaryKey columnNames="CLIENT_SESSION, AUTHENTICATOR" constraintName="CONSTRAINT_AUTH_STATUS_PK" tableName="CLIENT_SESSION_AUTH_STATUS"/>
+        <addForeignKeyConstraint baseColumnNames="CLIENT_SESSION" baseTableName="CLIENT_SESSION_AUTH_STATUS" constraintName="AUTH_STATUS_CONSTRAINT" referencedColumnNames="ID" referencedTableName="CLIENT_SESSION"/>
+    </changeSet>
+</databaseChangeLog>
diff --git a/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-master.xml b/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-master.xml
index 33eb5a1..8272af8 100755
--- a/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-master.xml
+++ b/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-master.xml
@@ -5,4 +5,5 @@
     <include file="META-INF/jpa-changelog-1.1.0.Final.xml"/>
     <include file="META-INF/jpa-changelog-1.2.0.Beta1.xml"/>
     <include file="META-INF/jpa-changelog-1.2.0.CR1.xml"/>
+    <include file="META-INF/jpa-changelog-1.3.0.Beta1.xml"/>
 </databaseChangeLog>
diff --git a/model/api/src/main/java/org/keycloak/models/AuthenticatorModel.java b/model/api/src/main/java/org/keycloak/models/AuthenticatorModel.java
new file mode 100755
index 0000000..0ca9322
--- /dev/null
+++ b/model/api/src/main/java/org/keycloak/models/AuthenticatorModel.java
@@ -0,0 +1,127 @@
+package org.keycloak.models;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+* @version $Revision: 1 $
+*/
+public class AuthenticatorModel {
+
+    public enum Requirement {
+        REQUIRED,
+        OPTIONAL,
+        ALTERNATIVE
+    }
+
+    private String id;
+    private String alias;
+    private String providerId;
+    private boolean masterAuthenticator;
+    private boolean formBased;
+    private String inputPage;
+    private String actionUrl;
+    private String setupUrl;
+    private Requirement requirement;
+    private boolean userSetupAllowed;
+    private int priority;
+    private Map<String, String> config = new HashMap<String, String>();
+
+
+    public String getId() {
+        return id;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    public String getAlias() {
+        return alias;
+    }
+
+    public void setAlias(String alias) {
+        this.alias = alias;
+    }
+
+    public String getProviderId() {
+        return providerId;
+    }
+
+    public void setProviderId(String providerId) {
+        this.providerId = providerId;
+    }
+
+    public boolean isFormBased() {
+        return formBased;
+    }
+
+    public void setFormBased(boolean formBased) {
+        this.formBased = formBased;
+    }
+
+    public String getInputPage() {
+        return inputPage;
+    }
+
+    public void setInputPage(String inputPage) {
+        this.inputPage = inputPage;
+    }
+
+    public String getActionUrl() {
+        return actionUrl;
+    }
+
+    public void setActionUrl(String actionUrl) {
+        this.actionUrl = actionUrl;
+    }
+
+    public String getSetupUrl() {
+        return setupUrl;
+    }
+
+    public void setSetupUrl(String setupUrl) {
+        this.setupUrl = setupUrl;
+    }
+
+    public Requirement getRequirement() {
+        return requirement;
+    }
+
+    public void setRequirement(Requirement requirement) {
+        this.requirement = requirement;
+    }
+
+    public int getPriority() {
+        return priority;
+    }
+
+    public void setPriority(int priority) {
+        this.priority = priority;
+    }
+
+    public boolean isUserSetupAllowed() {
+        return userSetupAllowed;
+    }
+
+    public void setUserSetupAllowed(boolean userSetupAllowed) {
+        this.userSetupAllowed = userSetupAllowed;
+    }
+
+    public boolean isMasterAuthenticator() {
+        return masterAuthenticator;
+    }
+
+    public void setMasterAuthenticator(boolean masterAuthenticator) {
+        this.masterAuthenticator = masterAuthenticator;
+    }
+
+    public Map<String, String> getConfig() {
+        return config;
+    }
+
+    public void setConfig(Map<String, String> config) {
+        this.config = config;
+    }
+}
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 7c197a3..2c66df3 100755
--- a/model/api/src/main/java/org/keycloak/models/ClientSessionModel.java
+++ b/model/api/src/main/java/org/keycloak/models/ClientSessionModel.java
@@ -1,5 +1,6 @@
 package org.keycloak.models;
 
+import java.util.Map;
 import java.util.Set;
 
 /**
@@ -31,6 +32,14 @@ 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 UserModel getAuthenticatedUser();
+    public void setAuthenticatedUser(UserModel user);
+
+
+
     /**
      * Authentication request type, i.e. OAUTH, SAML 2.0, SAML 1.1, etc.
      *
diff --git a/model/api/src/main/java/org/keycloak/models/entities/UserEntity.java b/model/api/src/main/java/org/keycloak/models/entities/UserEntity.java
index d2a2098..064697b 100755
--- a/model/api/src/main/java/org/keycloak/models/entities/UserEntity.java
+++ b/model/api/src/main/java/org/keycloak/models/entities/UserEntity.java
@@ -24,7 +24,7 @@ public class UserEntity extends AbstractIdentifiableEntity {
     private List<String> roleIds;
 
     private Map<String, String> attributes;
-    private List<UserModel.RequiredAction> requiredActions;
+    private List<String> requiredActions;
     private List<CredentialEntity> credentials = new ArrayList<CredentialEntity>();
     private List<FederatedIdentityEntity> federatedIdentities;
     private String federationLink;
@@ -109,11 +109,11 @@ public class UserEntity extends AbstractIdentifiableEntity {
         this.attributes = attributes;
     }
 
-    public List<UserModel.RequiredAction> getRequiredActions() {
+    public List<String> getRequiredActions() {
         return requiredActions;
     }
 
-    public void setRequiredActions(List<UserModel.RequiredAction> requiredActions) {
+    public void setRequiredActions(List<String> requiredActions) {
         this.requiredActions = requiredActions;
     }
 
diff --git a/model/api/src/main/java/org/keycloak/models/UserModel.java b/model/api/src/main/java/org/keycloak/models/UserModel.java
index 6761920..2088abc 100755
--- a/model/api/src/main/java/org/keycloak/models/UserModel.java
+++ b/model/api/src/main/java/org/keycloak/models/UserModel.java
@@ -35,8 +35,12 @@ public interface UserModel {
 
     Map<String, String> getAttributes();
 
-    Set<RequiredAction> getRequiredActions();
-    
+    Set<String> getRequiredActions();
+
+    void addRequiredAction(String action);
+
+    void removeRequiredAction(String action);
+
     void addRequiredAction(RequiredAction action);
 
     void removeRequiredAction(RequiredAction action);
@@ -65,6 +69,14 @@ public interface UserModel {
 
     void updateCredentialDirectly(UserCredentialValueModel cred);
 
+    /**
+     * Is the use configured to use this credential type
+     *
+     * @param type
+     * @return
+     */
+    boolean configuredForCredentialType(String type);
+
     Set<RoleModel> getRealmRoleMappings();
     Set<RoleModel> getClientRoleMappings(ClientModel app);
     boolean hasRole(RoleModel role);
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 1db82b6..769fbca 100755
--- a/model/api/src/main/java/org/keycloak/models/UserSessionModel.java
+++ b/model/api/src/main/java/org/keycloak/models/UserSessionModel.java
@@ -1,6 +1,7 @@
 package org.keycloak.models;
 
 import java.util.List;
+import java.util.Map;
 
 /**
  * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
@@ -36,6 +37,13 @@ public interface UserSessionModel {
 
     List<ClientSessionModel> getClientSessions();
 
+    public static enum AuthenticatorStatus {
+        SUCCESS,
+        SETUP_REQUIRED,
+        ATTEMPTED,
+        SKIPPED
+    }
+
     public String getNote(String name);
     public void setNote(String name, String value);
     public void removeNote(String name);
diff --git a/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java b/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java
index 32f4e89..2a8da38 100755
--- a/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java
+++ b/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java
@@ -55,8 +55,8 @@ public class ModelToRepresentation {
         rep.setFederationLink(user.getFederationLink());
 
         List<String> reqActions = new ArrayList<String>();
-        for (UserModel.RequiredAction ra : user.getRequiredActions()){
-            reqActions.add(ra.name());
+        for (String ra : user.getRequiredActions()){
+            reqActions.add(ra);
         }
 
         rep.setRequiredActions(reqActions);
diff --git a/model/api/src/main/java/org/keycloak/models/utils/UserModelDelegate.java b/model/api/src/main/java/org/keycloak/models/utils/UserModelDelegate.java
index 8ac8f5b..f025647 100755
--- a/model/api/src/main/java/org/keycloak/models/utils/UserModelDelegate.java
+++ b/model/api/src/main/java/org/keycloak/models/utils/UserModelDelegate.java
@@ -73,11 +73,26 @@ public class UserModelDelegate implements UserModel {
     }
 
     @Override
-    public Set<RequiredAction> getRequiredActions() {
+    public Set<String> getRequiredActions() {
         return delegate.getRequiredActions();
     }
 
     @Override
+    public void addRequiredAction(String action) {
+        delegate.addRequiredAction(action);
+    }
+
+    @Override
+    public void removeRequiredAction(String action) {
+        delegate.removeRequiredAction(action);
+    }
+
+    @Override
+    public boolean configuredForCredentialType(String type) {
+        return delegate.configuredForCredentialType(type);
+    }
+
+    @Override
     public void addRequiredAction(RequiredAction action) {
         delegate.addRequiredAction(action);
     }
@@ -211,4 +226,5 @@ public class UserModelDelegate implements UserModel {
     public boolean revokeConsentForClient(String clientId) {
         return delegate.revokeConsentForClient(clientId);
     }
+
 }
diff --git a/model/file/src/main/java/org/keycloak/models/file/adapter/UserAdapter.java b/model/file/src/main/java/org/keycloak/models/file/adapter/UserAdapter.java
index 6e622cc..eb4277f 100755
--- a/model/file/src/main/java/org/keycloak/models/file/adapter/UserAdapter.java
+++ b/model/file/src/main/java/org/keycloak/models/file/adapter/UserAdapter.java
@@ -183,29 +183,54 @@ public class UserAdapter implements UserModel, Comparable {
     }
 
     @Override
-    public Set<RequiredAction> getRequiredActions() {
-        List<RequiredAction> requiredActions = user.getRequiredActions();
-        if (requiredActions == null) requiredActions = new ArrayList<RequiredAction>();
+    public Set<String> getRequiredActions() {
+        List<String> requiredActions = user.getRequiredActions();
+        if (requiredActions == null) requiredActions = new ArrayList<String>();
         return new HashSet(requiredActions);
     }
 
     @Override
     public void addRequiredAction(RequiredAction action) {
-        List<RequiredAction> requiredActions = user.getRequiredActions();
-        if (requiredActions == null) requiredActions = new ArrayList<RequiredAction>();
-        if (!requiredActions.contains(action)) requiredActions.add(action);
+        String actionName = action.name();
+        addRequiredAction(actionName);
+    }
+
+    @Override
+    public void addRequiredAction(String actionName) {
+        List<String> requiredActions = user.getRequiredActions();
+        if (requiredActions == null) requiredActions = new ArrayList<>();
+        if (!requiredActions.contains(actionName)) {
+            requiredActions.add(actionName);
+        }
         user.setRequiredActions(requiredActions);
     }
 
     @Override
     public void removeRequiredAction(RequiredAction action) {
-        List<RequiredAction> requiredActions = user.getRequiredActions();
+        String actionName = action.name();
+        removeRequiredAction(actionName);
+    }
+
+    @Override
+    public void removeRequiredAction(String actionName) {
+        List<String> requiredActions = user.getRequiredActions();
         if (requiredActions == null) return;
-        requiredActions.remove(action);
+        requiredActions.remove(actionName);
         user.setRequiredActions(requiredActions);
     }
 
     @Override
+    public boolean configuredForCredentialType(String type) {
+        List<UserCredentialValueModel> creds = getCredentialsDirectly();
+        for (UserCredentialValueModel cred : creds) {
+            if (cred.getType().equals(type)) return true;
+        }
+        return false;
+    }
+
+
+
+    @Override
     public boolean isTotp() {
         return user.isTotp();
     }
diff --git a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedUser.java b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedUser.java
index ca9a24c..eb9d418 100755
--- a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedUser.java
+++ b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedUser.java
@@ -28,8 +28,8 @@ public class CachedUser {
     private boolean enabled;
     private boolean totp;
     private String federationLink;
-    private Map<String, String> attributes = new HashMap<String, String>();
-    private Set<UserModel.RequiredAction> requiredActions = new HashSet<UserModel.RequiredAction>();
+    private Map<String, String> attributes = new HashMap<>();
+    private Set<String> requiredActions = new HashSet<>();
     private Set<String> roleMappings = new HashSet<String>();
 
 
@@ -96,7 +96,7 @@ public class CachedUser {
         return attributes;
     }
 
-    public Set<UserModel.RequiredAction> getRequiredActions() {
+    public Set<String> getRequiredActions() {
         return requiredActions;
     }
 
diff --git a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/UserAdapter.java b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/UserAdapter.java
index 10336fd..dc159ce 100755
--- a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/UserAdapter.java
+++ b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/UserAdapter.java
@@ -102,7 +102,7 @@ public class UserAdapter implements UserModel {
     }
 
     @Override
-    public Set<RequiredAction> getRequiredActions() {
+    public Set<String> getRequiredActions() {
         if (updated != null) return updated.getRequiredActions();
         return cached.getRequiredActions();
     }
@@ -120,6 +120,27 @@ public class UserAdapter implements UserModel {
     }
 
     @Override
+    public void addRequiredAction(String action) {
+        getDelegateForUpdate();
+        updated.addRequiredAction(action);
+    }
+
+    @Override
+    public void removeRequiredAction(String action) {
+        getDelegateForUpdate();
+        updated.removeRequiredAction(action);
+    }
+
+    @Override
+    public boolean configuredForCredentialType(String type) {
+        List<UserCredentialValueModel> creds = getCredentialsDirectly();
+        for (UserCredentialValueModel cred : creds) {
+            if (cred.getType().equals(type)) return true;
+        }
+        return false;
+    }
+
+    @Override
     public String getFirstName() {
         if (updated != null) return updated.getFirstName();
         return cached.getFirstName();
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserRequiredActionEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserRequiredActionEntity.java
index 1818b75..a583815 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserRequiredActionEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserRequiredActionEntity.java
@@ -33,14 +33,14 @@ public class UserRequiredActionEntity {
     protected UserEntity user;
 
     @Id
-    @Column(name="ACTION")
-    protected UserModel.RequiredAction action;
+    @Column(name="REQUIRED_ACTION")
+    protected String action;
 
-    public UserModel.RequiredAction getAction() {
+    public String getAction() {
         return action;
     }
 
-    public void setAction(UserModel.RequiredAction action) {
+    public void setAction(String action) {
         this.action = action;
     }
 
@@ -56,12 +56,12 @@ public class UserRequiredActionEntity {
 
         protected UserEntity user;
 
-        protected UserModel.RequiredAction action;
+        protected String action;
 
         public Key() {
         }
 
-        public Key(UserEntity user, UserModel.RequiredAction action) {
+        public Key(UserEntity user, String action) {
             this.user = user;
             this.action = action;
         }
@@ -70,7 +70,7 @@ public class UserRequiredActionEntity {
             return user;
         }
 
-        public UserModel.RequiredAction getAction() {
+        public String getAction() {
             return action;
         }
 
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/UserAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/UserAdapter.java
index 5f92104..7c9086b 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/UserAdapter.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/UserAdapter.java
@@ -139,8 +139,8 @@ public class UserAdapter implements UserModel {
     }
 
     @Override
-    public Set<RequiredAction> getRequiredActions() {
-        Set<RequiredAction> result = new HashSet<RequiredAction>();
+    public Set<String> getRequiredActions() {
+        Set<String> result = new HashSet<>();
         for (UserRequiredActionEntity attr : user.getRequiredActions()) {
             result.add(attr.getAction());
         }
@@ -149,13 +149,19 @@ public class UserAdapter implements UserModel {
 
     @Override
     public void addRequiredAction(RequiredAction action) {
+        String actionName = action.name();
+        addRequiredAction(actionName);
+    }
+
+    @Override
+    public void addRequiredAction(String actionName) {
         for (UserRequiredActionEntity attr : user.getRequiredActions()) {
-            if (attr.getAction().equals(action)) {
+            if (attr.getAction().equals(actionName)) {
                 return;
             }
         }
         UserRequiredActionEntity attr = new UserRequiredActionEntity();
-        attr.setAction(action);
+        attr.setAction(actionName);
         attr.setUser(user);
         em.persist(attr);
         user.getRequiredActions().add(attr);
@@ -163,10 +169,16 @@ public class UserAdapter implements UserModel {
 
     @Override
     public void removeRequiredAction(RequiredAction action) {
+        String actionName = action.name();
+        removeRequiredAction(actionName);
+    }
+
+    @Override
+    public void removeRequiredAction(String actionName) {
         Iterator<UserRequiredActionEntity> it = user.getRequiredActions().iterator();
         while (it.hasNext()) {
             UserRequiredActionEntity attr = it.next();
-            if (attr.getAction().equals(action)) {
+            if (attr.getAction().equals(actionName)) {
                 it.remove();
                 em.remove(attr);
             }
@@ -174,6 +186,15 @@ public class UserAdapter implements UserModel {
     }
 
     @Override
+    public boolean configuredForCredentialType(String type) {
+        List<UserCredentialValueModel> creds = getCredentialsDirectly();
+        for (UserCredentialValueModel cred : creds) {
+            if (cred.getType().equals(type)) return true;
+        }
+        return false;
+    }
+
+    @Override
     public String getFirstName() {
         return user.getFirstName();
     }
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/UserAdapter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/UserAdapter.java
index 1a4a9b5..f7895f2 100755
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/UserAdapter.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/UserAdapter.java
@@ -159,8 +159,8 @@ public class UserAdapter extends AbstractMongoAdapter<MongoUserEntity> implement
 
 
     @Override
-    public Set<RequiredAction> getRequiredActions() {
-        Set<RequiredAction> result = new HashSet<RequiredAction>();
+    public Set<String> getRequiredActions() {
+        Set<String> result = new HashSet<String>();
         if (user.getRequiredActions() != null) {
             result.addAll(user.getRequiredActions());
         }
@@ -169,12 +169,24 @@ public class UserAdapter extends AbstractMongoAdapter<MongoUserEntity> implement
 
     @Override
     public void addRequiredAction(RequiredAction action) {
-        getMongoStore().pushItemToList(user, "requiredActions", action, true, invocationContext);
+        String actionName = action.name();
+        addRequiredAction(actionName);
+    }
+
+    @Override
+    public void addRequiredAction(String actionName) {
+        getMongoStore().pushItemToList(user, "requiredActions", actionName, true, invocationContext);
     }
 
     @Override
     public void removeRequiredAction(RequiredAction action) {
-        getMongoStore().pullItemFromList(user, "requiredActions", action, invocationContext);
+        String actionName = action.name();
+        removeRequiredAction(actionName);
+    }
+
+    @Override
+    public void removeRequiredAction(String actionName) {
+        getMongoStore().pullItemFromList(user, "requiredActions", actionName, invocationContext);
     }
 
     @Override
@@ -321,6 +333,16 @@ public class UserAdapter extends AbstractMongoAdapter<MongoUserEntity> implement
     }
 
     @Override
+    public boolean configuredForCredentialType(String type) {
+        List<UserCredentialValueModel> creds = getCredentialsDirectly();
+        for (UserCredentialValueModel cred : creds) {
+            if (cred.getType().equals(type)) return true;
+        }
+        return false;
+    }
+
+
+    @Override
     public void updateCredentialDirectly(UserCredentialValueModel credModel) {
         CredentialEntity credentialEntity = getCredentialEntity(user, credModel.getType());
 
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
old mode 100644
new mode 100755
index cc2c8cc..ddf42d5
--- 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
@@ -5,11 +5,13 @@ import org.keycloak.models.ClientModel;
 import org.keycloak.models.ClientSessionModel;
 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.infinispan.entities.ClientSessionEntity;
 import org.keycloak.models.sessions.infinispan.entities.SessionEntity;
 
 import java.util.HashMap;
+import java.util.Map;
 import java.util.Set;
 
 /**
@@ -165,5 +167,30 @@ public class ClientSessionAdapter implements ClientSessionModel {
     void update() {
         provider.getTx().replace(cache, entity.getId(), entity);
     }
+    @Override
+    public Map<String, UserSessionModel.AuthenticatorStatus> getAuthenticators() {
+        return entity.getAuthenticatorStatus();
+    }
+
+    @Override
+    public void setAuthenticatorStatus(String authenticator, UserSessionModel.AuthenticatorStatus status) {
+        entity.getAuthenticatorStatus().put(authenticator, status);
+
+    }
+
+    @Override
+    public void setAuthenticatorStatus(Map<String, UserSessionModel.AuthenticatorStatus> status) {
+        entity.setAuthenticatorStatus(status);
+    }
+
+    @Override
+    public UserModel getAuthenticatedUser() {
+        return session.users().getUserById(entity.getAuthUserId(), realm);    }
+
+    @Override
+    public void setAuthenticatedUser(UserModel user) {
+        entity.setAuthUserId(user.getId());
+
+    }
 
 }
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 38d2ea5..cc8ce20 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,9 @@
 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;
 import java.util.Set;
 
@@ -27,6 +29,8 @@ public class ClientSessionEntity extends SessionEntity {
     private Set<String> roles;
     private Set<String> protocolMappers;
     private Map<String, String> notes;
+    private Map<String, UserSessionModel.AuthenticatorStatus> authenticatorStatus = new HashMap<>();
+    private String authUserId;
 
     public String getClient() {
         return client;
@@ -107,4 +111,20 @@ public class ClientSessionEntity extends SessionEntity {
     public void setNotes(Map<String, String> notes) {
         this.notes = notes;
     }
+
+    public Map<String, UserSessionModel.AuthenticatorStatus> getAuthenticatorStatus() {
+        return authenticatorStatus;
+    }
+
+    public void setAuthenticatorStatus(Map<String, UserSessionModel.AuthenticatorStatus> authenticatorStatus) {
+        this.authenticatorStatus = authenticatorStatus;
+    }
+
+    public String getAuthUserId() {
+        return authUserId;
+    }
+
+    public void setAuthUserId(String authUserId) {
+        this.authUserId = authUserId;
+    }
 }
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 74e795f..68e3b53 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
@@ -4,6 +4,7 @@ import org.keycloak.models.ClientModel;
 import org.keycloak.models.ClientSessionModel;
 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.ClientSessionEntity;
 import org.keycloak.models.sessions.jpa.entities.ClientSessionNoteEntity;
@@ -14,6 +15,7 @@ import org.keycloak.models.sessions.jpa.entities.UserSessionEntity;
 import javax.persistence.EntityManager;
 import java.util.HashSet;
 import java.util.Iterator;
+import java.util.Map;
 import java.util.Set;
 
 /**
@@ -210,4 +212,29 @@ public class ClientSessionAdapter implements ClientSessionModel {
             }
         }
     }
+
+    @Override
+    public Map<String, UserSessionModel.AuthenticatorStatus> getAuthenticators() {
+        return null;
+    }
+
+    @Override
+    public void setAuthenticatorStatus(String authenticator, UserSessionModel.AuthenticatorStatus status) {
+
+    }
+
+    @Override
+    public void setAuthenticatorStatus(Map<String, UserSessionModel.AuthenticatorStatus> status) {
+
+    }
+
+    @Override
+    public UserModel getAuthenticatedUser() {
+        return null;
+    }
+
+    @Override
+    public void setAuthenticatedUser(UserModel user) {
+
+    }
 }
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
new file mode 100755
index 0000000..49afbab
--- /dev/null
+++ b/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/entities/ClientSessionAuthStatusEntity.java
@@ -0,0 +1,111 @@
+package org.keycloak.models.sessions.jpa.entities;
+
+import org.keycloak.models.UserSessionModel;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.Id;
+import javax.persistence.IdClass;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.NamedQueries;
+import javax.persistence.NamedQuery;
+import javax.persistence.Table;
+import java.io.Serializable;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+@NamedQueries({
+        @NamedQuery(name = "removeClientSessionAuthStatusByUser", query="delete from ClientSessionAuthStatusEntity r where r.clientSession IN (select c from ClientSessionEntity c where c.session IN (select s from UserSessionEntity s where s.realmId = :realmId and s.userId = :userId))"),
+        @NamedQuery(name = "removeClientSessionAuthStatusByClient", query="delete from ClientSessionAuthStatusEntity r where r.clientSession IN (select c from ClientSessionEntity c where c.clientId = :clientId and c.realmId = :realmId)"),
+        @NamedQuery(name = "removeClientSessionAuthStatusByRealm", query="delete from ClientSessionAuthStatusEntity r where r.clientSession IN (select c from ClientSessionEntity c where c.realmId = :realmId)"),
+        @NamedQuery(name = "removeClientSessionAuthStatusByExpired", query = "delete from ClientSessionAuthStatusEntity r where r.clientSession IN (select c from ClientSessionEntity c where c.session IN (select s from UserSessionEntity s where s.realmId = :realmId and (s.started < :maxTime or s.lastSessionRefresh < :idleTime)))"),
+        @NamedQuery(name = "removeDetachedClientSessionAuthStatusByExpired", query = "delete from ClientSessionAuthStatusEntity r where r.clientSession IN (select c from ClientSessionEntity c where c.session IS NULL and c.realmId = :realmId and c.timestamp < :maxTime )")
+})
+@Table(name="CLIENT_SESSION_AUTH_STATUS")
+@Entity
+@IdClass(ClientSessionAuthStatusEntity.Key.class)
+public class ClientSessionAuthStatusEntity {
+
+    @Id
+    @ManyToOne(fetch= FetchType.LAZY)
+    @JoinColumn(name = "CLIENT_SESSION")
+    protected ClientSessionEntity clientSession;
+
+    @Id
+    @Column(name = "AUTHENTICATOR")
+    protected String authenticator;
+    @Column(name = "STATUS")
+    protected UserSessionModel.AuthenticatorStatus status;
+
+    public String getAuthenticator() {
+        return authenticator;
+    }
+
+    public void setAuthenticator(String authenticator) {
+        this.authenticator = authenticator;
+    }
+
+    public UserSessionModel.AuthenticatorStatus getStatus() {
+        return status;
+    }
+
+    public void setStatus(UserSessionModel.AuthenticatorStatus status) {
+        this.status = status;
+    }
+
+    public ClientSessionEntity getClientSession() {
+        return clientSession;
+    }
+
+    public void setClientSession(ClientSessionEntity clientSession) {
+        this.clientSession = clientSession;
+    }
+
+    public static class Key implements Serializable {
+
+        protected ClientSessionEntity clientSession;
+
+        protected String authenticator;
+
+        public Key() {
+        }
+
+        public Key(ClientSessionEntity clientSession, String authenticator) {
+            this.clientSession = clientSession;
+            this.authenticator = authenticator;
+        }
+
+        public ClientSessionEntity getClientSession() {
+            return clientSession;
+        }
+
+        public String getAuthenticator() {
+            return authenticator;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (o == null || getClass() != o.getClass()) return false;
+
+            Key key = (Key) o;
+
+            if (authenticator != null ? !authenticator.equals(key.authenticator) : key.authenticator != null) return false;
+            if (clientSession != null ? !clientSession.getId().equals(key.clientSession != null ? key.clientSession.getId() : null) : key.clientSession != null) return false;
+
+            return true;
+        }
+
+        @Override
+        public int hashCode() {
+            int result = clientSession != null ? clientSession.getId().hashCode() : 0;
+            result = 31 * result + (authenticator != null ? authenticator.hashCode() : 0);
+            return result;
+        }
+    }
+
+}
diff --git a/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/entities/ClientSessionEntity.java b/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/entities/ClientSessionEntity.java
index 6f0c535..7cc9062 100755
--- a/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/entities/ClientSessionEntity.java
+++ b/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/entities/ClientSessionEntity.java
@@ -57,6 +57,9 @@ public class ClientSessionEntity {
     @Column(name="ACTION")
     protected ClientSessionModel.Action action;
 
+    @Column(name="AUTH_USER_ID")
+    protected String userId;
+
     @OneToMany(cascade = CascadeType.REMOVE, orphanRemoval = true, mappedBy="clientSession")
     protected Collection<ClientSessionRoleEntity> roles = new ArrayList<ClientSessionRoleEntity>();
 
@@ -66,6 +69,9 @@ public class ClientSessionEntity {
     @OneToMany(cascade = CascadeType.REMOVE, orphanRemoval = true, mappedBy="clientSession")
     protected Collection<ClientSessionNoteEntity> notes = new ArrayList<ClientSessionNoteEntity>();
 
+    @OneToMany(cascade = CascadeType.REMOVE, orphanRemoval = true, mappedBy="clientSession")
+    protected Collection<ClientSessionAuthStatusEntity> authanticatorStatus = new ArrayList<>();
+
     public String getId() {
         return id;
     }
@@ -153,4 +159,20 @@ public class ClientSessionEntity {
     public void setAuthMethod(String authMethod) {
         this.authMethod = authMethod;
     }
+
+    public Collection<ClientSessionAuthStatusEntity> getAuthanticatorStatus() {
+        return authanticatorStatus;
+    }
+
+    public void setAuthanticatorStatus(Collection<ClientSessionAuthStatusEntity> authanticatorStatus) {
+        this.authanticatorStatus = authanticatorStatus;
+    }
+
+    public String getUserId() {
+        return userId;
+    }
+
+    public void setUserId(String userId) {
+        this.userId = userId;
+    }
 }
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 daa6e3f..cff4ca3 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
@@ -4,10 +4,12 @@ import org.keycloak.models.ClientModel;
 import org.keycloak.models.ClientSessionModel;
 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.mem.entities.ClientSessionEntity;
 import org.keycloak.models.sessions.mem.entities.UserSessionEntity;
 
+import java.util.Map;
 import java.util.Set;
 
 /**
@@ -141,4 +143,30 @@ public class ClientSessionAdapter implements ClientSessionModel {
     public void setAuthMethod(String method) {
         entity.setAuthMethod(method);
     }
+
+    @Override
+    public Map<String, UserSessionModel.AuthenticatorStatus> getAuthenticators() {
+        return entity.getAuthenticatorStatus();
+    }
+
+    @Override
+    public void setAuthenticatorStatus(String authenticator, UserSessionModel.AuthenticatorStatus status) {
+        entity.getAuthenticatorStatus().put(authenticator, status);
+
+    }
+
+    @Override
+    public void setAuthenticatorStatus(Map<String, UserSessionModel.AuthenticatorStatus> status) {
+        entity.setAuthenticatorStatus(status);
+    }
+
+    @Override
+    public UserModel getAuthenticatedUser() {
+        return session.users().getUserById(entity.getAuthUserId(), realm);    }
+
+    @Override
+    public void setAuthenticatedUser(UserModel user) {
+        entity.setAuthUserId(user.getId());
+
+    }
 }
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 ca30496..9823570 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,6 +1,7 @@
 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;
@@ -14,6 +15,8 @@ public class ClientSessionEntity {
     private String id;
     private String clientId;
     private String realmId;
+    private Map<String, UserSessionModel.AuthenticatorStatus> authenticatorStatus = new HashMap<>();
+    private String authUserId;
 
     private UserSessionEntity session;
 
@@ -24,7 +27,7 @@ public class ClientSessionEntity {
     private ClientSessionModel.Action action;
     private Set<String> roles;
     private Set<String> protocolMappers;
-    private Map<String, String> notes = new HashMap<String, String>();
+    private Map<String, String> notes = new HashMap<>();
 
     public String getId() {
         return id;
@@ -109,4 +112,20 @@ public class ClientSessionEntity {
     public void setAuthMethod(String authMethod) {
         this.authMethod = authMethod;
     }
+
+    public String getAuthUserId() {
+        return authUserId;
+    }
+
+    public void setAuthUserId(String authUserId) {
+        this.authUserId = authUserId;
+    }
+
+    public Map<String, UserSessionModel.AuthenticatorStatus> getAuthenticatorStatus() {
+        return authenticatorStatus;
+    }
+
+    public void setAuthenticatorStatus(Map<String, UserSessionModel.AuthenticatorStatus> 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 649ffcf..e5fd346 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
@@ -5,6 +5,7 @@ import org.keycloak.models.ClientModel;
 import org.keycloak.models.ClientSessionModel;
 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.mongo.entities.MongoClientSessionEntity;
 import org.keycloak.models.sessions.mongo.entities.MongoUserSessionEntity;
@@ -12,6 +13,7 @@ import org.keycloak.models.sessions.mongo.entities.MongoUserSessionEntity;
 import java.util.HashSet;
 import java.util.LinkedList;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 
 /**
@@ -155,6 +157,37 @@ public class ClientSessionAdapter extends AbstractMongoAdapter<MongoClientSessio
     }
 
     @Override
+    public Map<String, UserSessionModel.AuthenticatorStatus> getAuthenticators() {
+        return entity.getAuthenticatorStatus();
+    }
+
+    @Override
+    public void setAuthenticatorStatus(String authenticator, UserSessionModel.AuthenticatorStatus status) {
+        entity.getAuthenticatorStatus().put(authenticator, status);
+        updateMongoEntity();
+
+    }
+
+    @Override
+    public void setAuthenticatorStatus(Map<String, UserSessionModel.AuthenticatorStatus> status) {
+        entity.setAuthenticatorStatus(status);
+        updateMongoEntity();
+
+    }
+
+    @Override
+    public UserModel getAuthenticatedUser() {
+        return session.users().getUserById(entity.getAuthUserId(), realm);
+    }
+
+    @Override
+    public void setAuthenticatedUser(UserModel user) {
+        entity.setAuthUserId(user.getId());
+        updateMongoEntity();
+
+    }
+
+    @Override
     public String getAuthMethod() {
         return entity.getAuthMethod();
     }
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 b5abede..ed8099d 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,6 +4,7 @@ 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;
@@ -29,6 +30,8 @@ public class MongoClientSessionEntity extends AbstractIdentifiableEntity impleme
     private List<String> roles;
     private List<String> protocolMappers;
     private Map<String, String> notes = new HashMap<String, String>();
+    private Map<String, UserSessionModel.AuthenticatorStatus> authenticatorStatus = new HashMap<>();
+    private String authUserId;
 
     public String getId() {
         return id;
@@ -118,6 +121,22 @@ public class MongoClientSessionEntity extends AbstractIdentifiableEntity impleme
         this.sessionId = sessionId;
     }
 
+    public Map<String, UserSessionModel.AuthenticatorStatus> getAuthenticatorStatus() {
+        return authenticatorStatus;
+    }
+
+    public void setAuthenticatorStatus(Map<String, UserSessionModel.AuthenticatorStatus> authenticatorStatus) {
+        this.authenticatorStatus = authenticatorStatus;
+    }
+
+    public String getAuthUserId() {
+        return authUserId;
+    }
+
+    public void setAuthUserId(String authUserId) {
+        this.authUserId = authUserId;
+    }
+
     @Override
     public void afterRemove(MongoStoreInvocationContext context) {
     }
diff --git a/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java b/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java
new file mode 100755
index 0000000..6f63004
--- /dev/null
+++ b/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java
@@ -0,0 +1,358 @@
+package org.keycloak.authentication;
+
+import org.jboss.resteasy.spi.HttpRequest;
+import org.keycloak.ClientConnection;
+import org.keycloak.events.EventBuilder;
+import org.keycloak.models.AuthenticatorModel;
+import org.keycloak.models.ClientSessionModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.models.UserSessionModel;
+import org.keycloak.protocol.oidc.TokenManager;
+import org.keycloak.services.managers.AuthenticationManager;
+import org.keycloak.services.managers.BruteForceProtector;
+
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriInfo;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+
+//
+// setup
+// cookie: master, alternative
+// CERT_AUTH: alternative
+// UserPassword: alternative
+//    OTP: optional
+//    CAPTHA: required
+//
+// scenario: username password
+// * cookie, attempted
+// * cert, attempated
+// * usernamepassord, doesn't see form, sets challenge to form
+//
+//
+//
+//
+//
+//
+//
+
+
+
+public class AuthenticationProcessor {
+    protected RealmModel realm;
+    protected UserSessionModel userSession;
+    protected ClientSessionModel clientSession;
+    protected ClientConnection connection;
+    protected UriInfo uriInfo;
+    protected KeycloakSession session;
+    protected List<AuthenticatorModel> authenticators;
+    protected BruteForceProtector protector;
+    protected EventBuilder eventBuilder;
+    protected HttpRequest request;
+
+
+    public static enum Status {
+        SUCCESS,
+        CHALLENGE,
+        FAILURE_CHALLENGE,
+        FAILED,
+        ATTEMPTED
+
+    }
+    public static enum Error {
+        INVALID_USER,
+        INVALID_CREDENTIALS,
+        CREDENTIAL_SETUP_REQUIRED,
+        USER_DISABLED,
+        USER_CONFLICT,
+        USER_TEMPORARILY_DISABLED,
+        INTERNAL_ERROR,
+        UNKNOWN_USER
+    }
+
+    public RealmModel getRealm() {
+        return realm;
+    }
+
+    public ClientSessionModel getClientSession() {
+        return clientSession;
+    }
+
+    public ClientConnection getConnection() {
+        return connection;
+    }
+
+    public UriInfo getUriInfo() {
+        return uriInfo;
+    }
+
+    public KeycloakSession getSession() {
+        return session;
+    }
+
+
+    private class Result implements AuthenticatorContext {
+        AuthenticatorModel model;
+        Authenticator authenticator;
+        Status status;
+        Response challenge;
+        Error error;
+
+        private Result(AuthenticatorModel model, Authenticator authenticator) {
+            this.model = model;
+            this.authenticator = authenticator;
+        }
+
+        @Override
+        public AuthenticatorModel getModel() {
+            return model;
+        }
+
+        @Override
+        public void setModel(AuthenticatorModel model) {
+            this.model = model;
+        }
+
+        @Override
+        public Authenticator getAuthenticator() {
+            return authenticator;
+        }
+
+        @Override
+        public void setAuthenticator(Authenticator authenticator) {
+            this.authenticator = authenticator;
+        }
+
+        @Override
+        public Status getStatus() {
+            return status;
+        }
+
+        @Override
+        public void success() {
+            this.status = Status.SUCCESS;
+        }
+
+        @Override
+        public void failure(Error error) {
+            status = Status.FAILED;
+            this.error = error;
+
+        }
+
+        @Override
+        public void challenge(Response challenge) {
+            this.status = Status.CHALLENGE;
+            this.challenge = challenge;
+
+        }
+        @Override
+        public void failureChallenge(Error error, Response challenge) {
+            this.error = error;
+            this.status = Status.FAILURE_CHALLENGE;
+            this.challenge = challenge;
+
+        }
+
+        @Override
+        public void attempted() {
+            this.status = Status.ATTEMPTED;
+
+        }
+
+        @Override
+        public UserModel getUser() {
+            return getClientSession().getAuthenticatedUser();
+        }
+
+        @Override
+        public void setUser(UserModel user) {
+            UserModel previousUser = getUser();
+            if (previousUser != null && !user.getId().equals(previousUser.getId())) throw new AuthException(Error.USER_CONFLICT);
+            validateUser(user);
+            getClientSession().setAuthenticatedUser(user);
+        }
+
+        @Override
+        public RealmModel getRealm() {
+            return AuthenticationProcessor.this.getRealm();
+        }
+
+        @Override
+        public ClientSessionModel getClientSession() {
+            return AuthenticationProcessor.this.getClientSession();
+        }
+
+        @Override
+        public ClientConnection getConnection() {
+            return AuthenticationProcessor.this.getConnection();
+        }
+
+        @Override
+        public UriInfo getUriInfo() {
+            return AuthenticationProcessor.this.getUriInfo();
+        }
+
+        @Override
+        public KeycloakSession getSession() {
+            return AuthenticationProcessor.this.getSession();
+        }
+
+        @Override
+        public HttpRequest getHttpRequest() {
+            return AuthenticationProcessor.this.request;
+        }
+
+        @Override
+        public void attachUserSession(UserSessionModel userSession) {
+            AuthenticationProcessor.this.userSession = userSession;
+        }
+
+        @Override
+        public BruteForceProtector getProtector() {
+            return AuthenticationProcessor.this.protector;
+        }
+    }
+
+    public static class AuthException extends RuntimeException {
+        private Error error;
+
+        public AuthException(Error error) {
+            this.error = error;
+        }
+
+        public AuthException(String message, Error error) {
+            super(message);
+            this.error = error;
+        }
+
+        public AuthException(String message, Throwable cause, Error error) {
+            super(message, cause);
+            this.error = error;
+        }
+
+        public AuthException(Throwable cause, Error error) {
+            super(cause);
+            this.error = error;
+        }
+
+        public AuthException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace, Error error) {
+            super(message, cause, enableSuppression, writableStackTrace);
+            this.error = error;
+        }
+    }
+
+    public void logUserFailure() {
+
+    }
+
+    protected boolean isProcessed(UserSessionModel.AuthenticatorStatus status) {
+        return status == UserSessionModel.AuthenticatorStatus.SUCCESS || status == UserSessionModel.AuthenticatorStatus.SKIPPED
+                || status == UserSessionModel.AuthenticatorStatus.ATTEMPTED
+                || status == UserSessionModel.AuthenticatorStatus.SETUP_REQUIRED;
+    }
+
+    public Response authenticate() {
+        UserModel authUser = clientSession.getAuthenticatedUser();
+        validateUser(authUser);
+        Response challenge = null;
+        Map<String, UserSessionModel.AuthenticatorStatus> previousAttempts = clientSession.getAuthenticators();
+        for (AuthenticatorModel model : authenticators) {
+            UserSessionModel.AuthenticatorStatus oldStatus = previousAttempts.get(model.getAlias());
+            if (isProcessed(oldStatus)) continue;
+
+            AuthenticatorFactory factory = (AuthenticatorFactory)session.getKeycloakSessionFactory().getProviderFactory(Authenticator.class, model.getProviderId());
+            Authenticator authenticator = factory.create(model);
+            if (authenticator.requiresUser() && authUser == null){
+                if ( authenticator.requiresUser()) {
+                    if (challenge != null) return challenge;
+                    throw new AuthException(Error.UNKNOWN_USER);
+                }
+            }
+            if (authUser != null && model.getRequirement() == AuthenticatorModel.Requirement.ALTERNATIVE) {
+                clientSession.setAuthenticatorStatus(model.getAlias(), UserSessionModel.AuthenticatorStatus.SKIPPED);
+                continue;
+            }
+            authUser = clientSession.getAuthenticatedUser();
+
+            if (authenticator.requiresUser() && authUser != null && !authenticator.configuredFor(authUser)) {
+                if (model.getRequirement() == AuthenticatorModel.Requirement.REQUIRED) {
+                    if (model.isUserSetupAllowed()) {
+                        clientSession.setAuthenticatorStatus(model.getAlias(), UserSessionModel.AuthenticatorStatus.SETUP_REQUIRED);
+                        authUser.addRequiredAction(authenticator.getRequiredAction());
+
+                    } else {
+                        throw new AuthException(Error.CREDENTIAL_SETUP_REQUIRED);
+                    }
+                }
+                continue;
+            }
+            Result context = new Result(model, authenticator);
+            authenticator.authenticate(context);
+            Status result = context.getStatus();
+            if (result == Status.SUCCESS){
+                clientSession.setAuthenticatorStatus(model.getAlias(), UserSessionModel.AuthenticatorStatus.SUCCESS);
+                if (model.isMasterAuthenticator()) return authenticationComplete();
+                continue;
+            } else if (result == Status.FAILED) {
+                throw new AuthException(context.error);
+            } else if (result == Status.CHALLENGE) {
+                if (model.getRequirement() == AuthenticatorModel.Requirement.REQUIRED) return context.challenge;
+                if (challenge != null) challenge = context.challenge;
+                continue;
+            } else if (result == Status.FAILURE_CHALLENGE) {
+                logUserFailure();
+                return context.challenge;
+            } else if (result == Status.ATTEMPTED) {
+                if (model.getRequirement() == AuthenticatorModel.Requirement.REQUIRED) throw new AuthException(Error.INVALID_CREDENTIALS);
+                clientSession.setAuthenticatorStatus(model.getAlias(), UserSessionModel.AuthenticatorStatus.ATTEMPTED);
+                continue;
+            }
+        }
+
+        if (authUser == null) {
+            if (challenge != null) return challenge;
+            throw new AuthException(Error.UNKNOWN_USER);
+        }
+
+
+        return authenticationComplete();
+    }
+
+
+
+    public void validateUser(UserModel authenticatedUser) {
+        if (authenticatedUser != null) {
+            if (!clientSession.getAuthenticatedUser().isEnabled()) throw new AuthException(Error.USER_DISABLED);
+        }
+        if (realm.isBruteForceProtected()) {
+            if (protector.isTemporarilyDisabled(session, realm, authenticatedUser.getUsername())) {
+                throw new AuthException(Error.USER_TEMPORARILY_DISABLED);
+            }
+        }
+    }
+
+    protected  Response authenticationComplete() {
+        if (userSession == null) { // if no authenticator attached a usersession
+            userSession = session.sessions().createUserSession(realm, clientSession.getAuthenticatedUser(), clientSession.getAuthenticatedUser().getUsername(), connection.getRemoteAddr(), "form", false, null, null);
+            userSession.setState(UserSessionModel.State.LOGGING_IN);
+        }
+        TokenManager.attachClientSession(userSession, clientSession);
+        return processRequiredActions();
+
+    }
+
+    public Response processRequiredActions() {
+        return AuthenticationManager.nextActionAfterAuthentication(session, userSession, clientSession, connection, request, uriInfo, eventBuilder);
+
+    }
+
+
+}
diff --git a/services/src/main/java/org/keycloak/authentication/Authenticator.java b/services/src/main/java/org/keycloak/authentication/Authenticator.java
new file mode 100755
index 0000000..3d5c64e
--- /dev/null
+++ b/services/src/main/java/org/keycloak/authentication/Authenticator.java
@@ -0,0 +1,17 @@
+package org.keycloak.authentication;
+
+import org.keycloak.models.UserModel;
+import org.keycloak.provider.Provider;
+
+/**
+* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+* @version $Revision: 1 $
+*/
+public interface Authenticator extends Provider {
+    boolean requiresUser();
+    void authenticate(AuthenticatorContext context);
+    boolean configuredFor(UserModel user);
+    String getRequiredAction();
+
+
+}
diff --git a/services/src/main/java/org/keycloak/authentication/AuthenticatorContext.java b/services/src/main/java/org/keycloak/authentication/AuthenticatorContext.java
new file mode 100755
index 0000000..162c9b6
--- /dev/null
+++ b/services/src/main/java/org/keycloak/authentication/AuthenticatorContext.java
@@ -0,0 +1,54 @@
+package org.keycloak.authentication;
+
+import org.jboss.resteasy.spi.HttpRequest;
+import org.keycloak.ClientConnection;
+import org.keycloak.models.AuthenticatorModel;
+import org.keycloak.models.ClientSessionModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.models.UserSessionModel;
+import org.keycloak.services.managers.BruteForceProtector;
+
+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 AuthenticatorContext {
+    AuthenticatorModel getModel();
+
+    void setModel(AuthenticatorModel model);
+
+    Authenticator getAuthenticator();
+
+    void setAuthenticator(Authenticator authenticator);
+
+    AuthenticationProcessor.Status getStatus();
+
+    UserModel getUser();
+
+    void setUser(UserModel user);
+
+    RealmModel getRealm();
+
+    ClientSessionModel getClientSession();
+    void attachUserSession(UserSessionModel userSession);
+
+    ClientConnection getConnection();
+
+    UriInfo getUriInfo();
+
+    KeycloakSession getSession();
+
+    HttpRequest getHttpRequest();
+    BruteForceProtector getProtector();
+
+    void success();
+    void failure(AuthenticationProcessor.Error error);
+    void challenge(Response challenge);
+    void failureChallenge(AuthenticationProcessor.Error error, Response challenge);
+    void attempted();
+}
diff --git a/services/src/main/java/org/keycloak/authentication/AuthenticatorFactory.java b/services/src/main/java/org/keycloak/authentication/AuthenticatorFactory.java
new file mode 100755
index 0000000..28290fc
--- /dev/null
+++ b/services/src/main/java/org/keycloak/authentication/AuthenticatorFactory.java
@@ -0,0 +1,13 @@
+package org.keycloak.authentication;
+
+import org.keycloak.models.AuthenticatorModel;
+import org.keycloak.provider.ProviderFactory;
+
+/**
+* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+* @version $Revision: 1 $
+*/
+public interface AuthenticatorFactory extends ProviderFactory<Authenticator> {
+    Authenticator create(AuthenticatorModel model);
+
+}
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/CookieAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/CookieAuthenticator.java
new file mode 100755
index 0000000..b508024
--- /dev/null
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/CookieAuthenticator.java
@@ -0,0 +1,47 @@
+package org.keycloak.authentication.authenticators;
+
+import org.keycloak.authentication.Authenticator;
+import org.keycloak.authentication.AuthenticatorContext;
+import org.keycloak.models.UserModel;
+import org.keycloak.services.managers.AuthenticationManager;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class CookieAuthenticator implements Authenticator {
+
+    @Override
+    public boolean requiresUser() {
+        return false;
+    }
+
+    @Override
+    public void authenticate(AuthenticatorContext context) {
+        AuthenticationManager.AuthResult authResult = AuthenticationManager.authenticateIdentityCookie(context.getSession(),
+                context.getRealm(), context.getUriInfo(), context.getConnection(), context.getHttpRequest().getHttpHeaders(), true);
+        if (authResult == null) {
+            context.attempted();
+        } else {
+            context.setUser(authResult.getUser());
+            context.attachUserSession(authResult.getSession());
+            context.success();
+        }
+
+    }
+
+    @Override
+    public boolean configuredFor(UserModel user) {
+        return true;
+    }
+
+    @Override
+    public String getRequiredAction() {
+        return null;
+    }
+
+    @Override
+    public void close() {
+
+    }
+}
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/CookieAuthenticatorFactory.java b/services/src/main/java/org/keycloak/authentication/authenticators/CookieAuthenticatorFactory.java
new file mode 100755
index 0000000..ab50b3d
--- /dev/null
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/CookieAuthenticatorFactory.java
@@ -0,0 +1,45 @@
+package org.keycloak.authentication.authenticators;
+
+import org.keycloak.Config;
+import org.keycloak.authentication.Authenticator;
+import org.keycloak.authentication.AuthenticatorFactory;
+import org.keycloak.models.AuthenticatorModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakSessionFactory;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class CookieAuthenticatorFactory implements AuthenticatorFactory {
+    static CookieAuthenticator SINGLETON = new CookieAuthenticator();
+    @Override
+    public Authenticator create(AuthenticatorModel model) {
+        return SINGLETON;
+    }
+
+    @Override
+    public Authenticator create(KeycloakSession session) {
+        throw new IllegalStateException("illegal call");
+    }
+
+    @Override
+    public void init(Config.Scope config) {
+
+    }
+
+    @Override
+    public void postInit(KeycloakSessionFactory factory) {
+
+    }
+
+    @Override
+    public void close() {
+
+    }
+
+    @Override
+    public String getId() {
+        return "auth-cookie";
+    }
+}
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/LoginFormOTPAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/LoginFormOTPAuthenticator.java
new file mode 100755
index 0000000..7cf5b71
--- /dev/null
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/LoginFormOTPAuthenticator.java
@@ -0,0 +1,71 @@
+package org.keycloak.authentication.authenticators;
+
+import org.keycloak.authentication.AuthenticationProcessor;
+import org.keycloak.authentication.AuthenticatorContext;
+import org.keycloak.models.AuthenticatorModel;
+import org.keycloak.models.UserCredentialModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.representations.idm.CredentialRepresentation;
+
+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>
+ * @version $Revision: 1 $
+ */
+public class LoginFormOTPAuthenticator extends LoginFormUsernameAuthenticator {
+
+    public LoginFormOTPAuthenticator(AuthenticatorModel model) {
+        super(model);
+    }
+
+    @Override
+    public void authenticate(AuthenticatorContext context) {
+        if (!isActionUrl(context)) {
+            context.failure(AuthenticationProcessor.Error.INTERNAL_ERROR);
+            return;
+        }
+        validateOTP(context);
+    }
+
+    public void validateOTP(AuthenticatorContext context) {
+        MultivaluedMap<String, String> inputData = context.getHttpRequest().getFormParameters();
+        List<UserCredentialModel> credentials = new LinkedList<>();
+        String password = inputData.getFirst(CredentialRepresentation.TOTP);
+        if (password == null) {
+            Response challengeResponse = challenge(context);
+            context.failureChallenge(AuthenticationProcessor.Error.INVALID_CREDENTIALS, challengeResponse);
+            return;
+        }
+        credentials.add(UserCredentialModel.totp(password));
+        boolean valid = context.getSession().users().validCredentials(context.getRealm(), context.getUser(), credentials);
+        if (!valid) {
+            Response challengeResponse = challenge(context);
+            context.failureChallenge(AuthenticationProcessor.Error.INVALID_CREDENTIALS, challengeResponse);
+            return;
+        }
+    }
+
+    @Override
+    public boolean requiresUser() {
+        return true;
+    }
+
+    @Override
+    public boolean configuredFor(UserModel user) {
+        return user.configuredForCredentialType(UserCredentialModel.TOTP);
+    }
+
+    @Override
+    public String getRequiredAction() {
+        return UserModel.RequiredAction.CONFIGURE_TOTP.name();
+    }
+
+    @Override
+    public void close() {
+
+    }
+}
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/LoginFormPasswordAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/LoginFormPasswordAuthenticator.java
new file mode 100755
index 0000000..1aae3b2
--- /dev/null
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/LoginFormPasswordAuthenticator.java
@@ -0,0 +1,71 @@
+package org.keycloak.authentication.authenticators;
+
+import org.keycloak.authentication.AuthenticationProcessor;
+import org.keycloak.authentication.AuthenticatorContext;
+import org.keycloak.models.AuthenticatorModel;
+import org.keycloak.models.UserCredentialModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.representations.idm.CredentialRepresentation;
+
+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>
+ * @version $Revision: 1 $
+ */
+public class LoginFormPasswordAuthenticator extends LoginFormUsernameAuthenticator {
+
+    public LoginFormPasswordAuthenticator(AuthenticatorModel model) {
+        super(model);
+    }
+
+    @Override
+    public void authenticate(AuthenticatorContext context) {
+        if (!isActionUrl(context)) {
+            context.failure(AuthenticationProcessor.Error.INTERNAL_ERROR);
+            return;
+        }
+        validatePassword(context);
+    }
+
+    public void validatePassword(AuthenticatorContext context) {
+        MultivaluedMap<String, String> inputData = context.getHttpRequest().getFormParameters();
+        List<UserCredentialModel> credentials = new LinkedList<>();
+        String password = inputData.getFirst(CredentialRepresentation.PASSWORD);
+        if (password == null) {
+            Response challengeResponse = challenge(context);
+            context.failureChallenge(AuthenticationProcessor.Error.INVALID_CREDENTIALS, challengeResponse);
+            return;
+        }
+        credentials.add(UserCredentialModel.password(password));
+        boolean valid = context.getSession().users().validCredentials(context.getRealm(), context.getUser(), credentials);
+        if (!valid) {
+            Response challengeResponse = challenge(context);
+            context.failureChallenge(AuthenticationProcessor.Error.INVALID_CREDENTIALS, challengeResponse);
+            return;
+        }
+    }
+
+    @Override
+    public boolean requiresUser() {
+        return true;
+    }
+
+    @Override
+    public boolean configuredFor(UserModel user) {
+        return false;
+    }
+
+    @Override
+    public String getRequiredAction() {
+        return UserModel.RequiredAction.UPDATE_PASSWORD.name();
+    }
+
+    @Override
+    public void close() {
+
+    }
+}
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/LoginFormUsernameAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/LoginFormUsernameAuthenticator.java
new file mode 100755
index 0000000..1d1f73f
--- /dev/null
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/LoginFormUsernameAuthenticator.java
@@ -0,0 +1,121 @@
+package org.keycloak.authentication.authenticators;
+
+import org.jboss.resteasy.specimpl.MultivaluedMapImpl;
+import org.keycloak.authentication.AuthenticationProcessor;
+import org.keycloak.authentication.Authenticator;
+import org.keycloak.authentication.AuthenticatorContext;
+import org.keycloak.login.LoginFormsProvider;
+import org.keycloak.models.AuthenticatorModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.models.utils.KeycloakModelUtils;
+import org.keycloak.protocol.oidc.OIDCLoginProtocol;
+import org.keycloak.services.managers.AuthenticationManager;
+import org.keycloak.services.managers.ClientSessionCode;
+import org.keycloak.services.resources.LoginActionsService;
+
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.core.Response;
+import java.net.URI;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class LoginFormUsernameAuthenticator implements Authenticator {
+    protected AuthenticatorModel model;
+
+    public LoginFormUsernameAuthenticator(AuthenticatorModel model) {
+        this.model = model;
+    }
+
+    @Override
+    public void authenticate(AuthenticatorContext context) {
+        if (!isActionUrl(context)) {
+            MultivaluedMap<String, String> formData = new MultivaluedMapImpl<>();
+            String loginHint = context.getClientSession().getNote(OIDCLoginProtocol.LOGIN_HINT_PARAM);
+            if (loginHint == null) {
+                loginHint = AuthenticationManager.getRememberMeUsername(context.getRealm(), context.getHttpRequest().getHttpHeaders());
+                if (loginHint != null) {
+                    formData.add("rememberMe", "on");
+                }
+            }
+            if (loginHint != null) formData.add(AuthenticationManager.FORM_USERNAME, loginHint);
+            Response challengeResponse = challenge(context, formData);
+            context.challenge(challengeResponse);
+            return;
+        }
+        validateUser(context);
+    }
+
+    protected boolean isActionUrl(AuthenticatorContext context) {
+        URI expected = LoginActionsService.authenticationFormProcessor(context.getUriInfo()).build(context.getRealm().getName());
+        return expected.getPath().equals(context.getUriInfo().getPath());
+
+    }
+
+    @Override
+    public boolean requiresUser() {
+        return false;
+    }
+
+    protected Response challenge(AuthenticatorContext context, MultivaluedMap<String, String> formData) {
+        LoginFormsProvider forms = context.getSession().getProvider(LoginFormsProvider.class)
+                .setClientSessionCode(new ClientSessionCode(context.getRealm(), context.getClientSession()).getCode());
+
+        if (formData.size() > 0) forms.setFormData(formData);
+
+        return forms.createLogin();
+    }
+
+    public void validateUser(AuthenticatorContext context) {
+        MultivaluedMap<String, String> inputData = context.getHttpRequest().getFormParameters();
+        String username = inputData.getFirst(AuthenticationManager.FORM_USERNAME);
+        if (username == null) {
+            Response challengeResponse = challenge(context);
+            context.failureChallenge(AuthenticationProcessor.Error.INVALID_USER, challengeResponse);
+            return;
+        }
+        UserModel user = KeycloakModelUtils.findUserByNameOrEmail(context.getSession(), context.getRealm(), username);
+        if (invalidUser(context, user)) return;
+        context.setUser(user);
+    }
+
+    public boolean invalidUser(AuthenticatorContext context, UserModel user) {
+        if (user == null) {
+            Response challengeResponse = challenge(context);
+            context.failureChallenge(AuthenticationProcessor.Error.INVALID_USER, challengeResponse);
+            return true;
+        }
+        if (!user.isEnabled()) {
+            context.failure(AuthenticationProcessor.Error.USER_DISABLED);
+            return true;
+        }
+        if (context.getRealm().isBruteForceProtected()) {
+            if (context.getProtector().isTemporarilyDisabled(context.getSession(), context.getRealm(), user.getUsername())) {
+                context.failure(AuthenticationProcessor.Error.USER_TEMPORARILY_DISABLED);
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public Response challenge(AuthenticatorContext context) {
+        MultivaluedMap<String, String> formData = new MultivaluedMapImpl<>();
+        return challenge(context, formData);
+    }
+
+    @Override
+    public boolean configuredFor(UserModel user) {
+        return true;
+    }
+
+    @Override
+    public String getRequiredAction() {
+        return null;
+    }
+
+    @Override
+    public void close() {
+
+    }
+}
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/OTPFormAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/OTPFormAuthenticator.java
new file mode 100755
index 0000000..7659c92
--- /dev/null
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/OTPFormAuthenticator.java
@@ -0,0 +1,97 @@
+package org.keycloak.authentication.authenticators;
+
+import org.jboss.resteasy.specimpl.MultivaluedMapImpl;
+import org.keycloak.authentication.AuthenticationProcessor;
+import org.keycloak.authentication.Authenticator;
+import org.keycloak.authentication.AuthenticatorContext;
+import org.keycloak.login.LoginFormsProvider;
+import org.keycloak.models.AuthenticatorModel;
+import org.keycloak.models.UserCredentialModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.protocol.oidc.OIDCLoginProtocol;
+import org.keycloak.representations.idm.CredentialRepresentation;
+import org.keycloak.services.managers.AuthenticationManager;
+import org.keycloak.services.managers.ClientSessionCode;
+import org.keycloak.services.resources.LoginActionsService;
+
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.core.Response;
+import java.net.URI;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class OTPFormAuthenticator implements Authenticator {
+    protected AuthenticatorModel model;
+
+    public OTPFormAuthenticator(AuthenticatorModel model) {
+        this.model = model;
+    }
+
+    @Override
+    public void authenticate(AuthenticatorContext context) {
+        URI expected = LoginActionsService.authenticationFormProcessor(context.getUriInfo()).build(context.getRealm().getName());
+        if (!expected.getPath().equals(context.getUriInfo().getPath())) {
+            Response challengeResponse = challenge(context);
+            context.challenge(challengeResponse);
+            return;
+        }
+        validateOTP(context);
+    }
+
+    public void validateOTP(AuthenticatorContext context) {
+        MultivaluedMap<String, String> inputData = context.getHttpRequest().getFormParameters();
+        List<UserCredentialModel> credentials = new LinkedList<>();
+        String password = inputData.getFirst(CredentialRepresentation.TOTP);
+        if (password == null) {
+            Response challengeResponse = challenge(context);
+            context.failureChallenge(AuthenticationProcessor.Error.INVALID_CREDENTIALS, challengeResponse);
+            return;
+        }
+        credentials.add(UserCredentialModel.totp(password));
+        boolean valid = context.getSession().users().validCredentials(context.getRealm(), context.getUser(), credentials);
+        if (!valid) {
+            Response challengeResponse = challenge(context);
+            context.failureChallenge(AuthenticationProcessor.Error.INVALID_CREDENTIALS, challengeResponse);
+            return;
+        }
+    }
+
+
+    @Override
+    public boolean requiresUser() {
+        return true;
+    }
+
+    protected Response challenge(AuthenticatorContext context, MultivaluedMap<String, String> formData) {
+        LoginFormsProvider forms = context.getSession().getProvider(LoginFormsProvider.class)
+                .setClientSessionCode(new ClientSessionCode(context.getRealm(), context.getClientSession()).getCode());
+
+        if (formData.size() > 0) forms.setFormData(formData);
+
+        return forms.createLoginTotp();
+    }
+
+    public Response challenge(AuthenticatorContext context) {
+        MultivaluedMap<String, String> formData = new MultivaluedMapImpl<>();
+        return challenge(context, formData);
+    }
+
+    @Override
+    public boolean configuredFor(UserModel user) {
+        return user.configuredForCredentialType(UserCredentialModel.TOTP);
+    }
+
+    @Override
+    public String getRequiredAction() {
+        return UserModel.RequiredAction.CONFIGURE_TOTP.name();
+    }
+
+    @Override
+    public void close() {
+
+    }
+}
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 be7291c..60a49d5 100755
--- a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
@@ -406,7 +406,7 @@ public class AuthenticationManager {
                 }
             }
         }
-
+        if (userSession.getState() != UserSessionModel.State.LOGGED_IN) userSession.setState(UserSessionModel.State.LOGGED_IN);
         // refresh the cookies!
         createLoginCookie(realm, userSession.getUser(), userSession, uriInfo, clientConnection);
         if (userSession.isRememberMe()) createRememberMeCookie(realm, userSession.getUser().getUsername(), uriInfo, clientConnection);
@@ -434,12 +434,12 @@ public class AuthenticationManager {
 
         event.detail(Details.CODE_ID, clientSession.getId());
 
-        Set<UserModel.RequiredAction> requiredActions = user.getRequiredActions();
+        Set<String> requiredActions = user.getRequiredActions();
         if (!requiredActions.isEmpty()) {
-            Iterator<RequiredAction> i = user.getRequiredActions().iterator();
-            UserModel.RequiredAction action = i.next();
+            Iterator<String> i = user.getRequiredActions().iterator();
+            String action = i.next();
             
-            if (action.equals(UserModel.RequiredAction.VERIFY_EMAIL) && Validation.isEmpty(user.getEmail())) {
+            if (action.equals(UserModel.RequiredAction.VERIFY_EMAIL.name()) && Validation.isEmpty(user.getEmail())) {
                 if (i.hasNext())
                     action = i.next();
                 else
@@ -447,16 +447,16 @@ public class AuthenticationManager {
             }
 
             if (action != null) {
-                accessCode.setRequiredAction(action);
+                accessCode.setRequiredAction(RequiredAction.valueOf(action));
 
                 LoginFormsProvider loginFormsProvider = session.getProvider(LoginFormsProvider.class).setClientSessionCode(accessCode.getCode())
                         .setUser(user);
-                if (action.equals(UserModel.RequiredAction.VERIFY_EMAIL)) {
+                if (action.equals(UserModel.RequiredAction.VERIFY_EMAIL.name())) {
                     event.clone().event(EventType.SEND_VERIFY_EMAIL).detail(Details.EMAIL, user.getEmail()).success();
                     LoginActionsService.createActionCookie(realm, uriInfo, clientConnection, userSession.getId());
                 }
 
-                return loginFormsProvider.createResponse(action);
+                return loginFormsProvider.createResponse(RequiredAction.valueOf(action));
             }
         }
 
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 4704943..6daf649 100755
--- a/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java
+++ b/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java
@@ -117,6 +117,10 @@ public class LoginActionsService {
         return loginActionsBaseUrl(baseUriBuilder);
     }
 
+    public static UriBuilder authenticationFormProcessor(UriInfo uriInfo) {
+        return loginActionsBaseUrl(uriInfo).path("auth-form");
+    }
+
     public static UriBuilder loginActionsBaseUrl(UriBuilder baseUriBuilder) {
         return baseUriBuilder.path(RealmsResource.class).path(RealmsResource.class, "getLoginActionsService");
     }
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractIdentityProviderTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractIdentityProviderTest.java
index b9b45a9..1ad0207 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractIdentityProviderTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractIdentityProviderTest.java
@@ -151,7 +151,7 @@ public abstract class AbstractIdentityProviderTest {
 
             UserModel federatedUser = assertSuccessfulAuthentication(identityProviderModel, "test-user-noemail", null);
 
-            assertTrue(federatedUser.getRequiredActions().contains(RequiredAction.VERIFY_EMAIL));
+            assertTrue(federatedUser.getRequiredActions().contains(RequiredAction.VERIFY_EMAIL.name()));
 
         } finally {
             getRealm().setVerifyEmail(false);
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/AbstractKerberosTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/AbstractKerberosTest.java
index 2580242..5e1930e 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/AbstractKerberosTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/AbstractKerberosTest.java
@@ -298,7 +298,7 @@ public abstract class AbstractKerberosTest {
             Assert.assertEquals(user.getLastName(), expectedLastname);
 
             if (updateProfileActionExpected) {
-                Assert.assertEquals(UserModel.RequiredAction.UPDATE_PROFILE.toString(), user.getRequiredActions().iterator().next().name());
+                Assert.assertEquals(UserModel.RequiredAction.UPDATE_PROFILE.toString(), user.getRequiredActions().iterator().next());
             } else {
                 Assert.assertTrue(user.getRequiredActions().isEmpty());
             }
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserModelTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserModelTest.java
index b70e3ff..d9e1aa5 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserModelTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserModelTest.java
@@ -110,28 +110,28 @@ public class UserModelTest extends AbstractModelTest {
         user = session.users().getUserByUsername("user", realm);
 
         Assert.assertEquals(1, user.getRequiredActions().size());
-        Assert.assertTrue(user.getRequiredActions().contains(RequiredAction.CONFIGURE_TOTP));
+        Assert.assertTrue(user.getRequiredActions().contains(RequiredAction.CONFIGURE_TOTP.name()));
 
         user.addRequiredAction(RequiredAction.CONFIGURE_TOTP);
         user = session.users().getUserByUsername("user", realm);
 
         Assert.assertEquals(1, user.getRequiredActions().size());
-        Assert.assertTrue(user.getRequiredActions().contains(RequiredAction.CONFIGURE_TOTP));
+        Assert.assertTrue(user.getRequiredActions().contains(RequiredAction.CONFIGURE_TOTP.name()));
 
-        user.addRequiredAction(RequiredAction.VERIFY_EMAIL);
+        user.addRequiredAction(RequiredAction.VERIFY_EMAIL.name());
         user = session.users().getUserByUsername("user", realm);
 
         Assert.assertEquals(2, user.getRequiredActions().size());
-        Assert.assertTrue(user.getRequiredActions().contains(RequiredAction.CONFIGURE_TOTP));
-        Assert.assertTrue(user.getRequiredActions().contains(RequiredAction.VERIFY_EMAIL));
+        Assert.assertTrue(user.getRequiredActions().contains(RequiredAction.CONFIGURE_TOTP.name()));
+        Assert.assertTrue(user.getRequiredActions().contains(RequiredAction.VERIFY_EMAIL.name()));
 
-        user.removeRequiredAction(RequiredAction.CONFIGURE_TOTP);
+        user.removeRequiredAction(RequiredAction.CONFIGURE_TOTP.name());
         user = session.users().getUserByUsername("user", realm);
 
         Assert.assertEquals(1, user.getRequiredActions().size());
-        Assert.assertTrue(user.getRequiredActions().contains(RequiredAction.VERIFY_EMAIL));
+        Assert.assertTrue(user.getRequiredActions().contains(RequiredAction.VERIFY_EMAIL.name()));
 
-        user.removeRequiredAction(RequiredAction.VERIFY_EMAIL);
+        user.removeRequiredAction(RequiredAction.VERIFY_EMAIL.name());
         user = session.users().getUserByUsername("user", realm);
 
         Assert.assertTrue(user.getRequiredActions().isEmpty());
@@ -142,9 +142,9 @@ public class UserModelTest extends AbstractModelTest {
         Assert.assertEquals(expected.getFirstName(), actual.getFirstName());
         Assert.assertEquals(expected.getLastName(), actual.getLastName());
 
-        RequiredAction[] expectedRequiredActions = expected.getRequiredActions().toArray(new RequiredAction[expected.getRequiredActions().size()]);
+        String[] expectedRequiredActions = expected.getRequiredActions().toArray(new String[expected.getRequiredActions().size()]);
         Arrays.sort(expectedRequiredActions);
-        RequiredAction[] actualRequiredActions = actual.getRequiredActions().toArray(new RequiredAction[actual.getRequiredActions().size()]);
+        String[] actualRequiredActions = actual.getRequiredActions().toArray(new String[actual.getRequiredActions().size()]);
         Arrays.sort(actualRequiredActions);
 
         Assert.assertArrayEquals(expectedRequiredActions, actualRequiredActions);