keycloak-aplcache

clientsession.action to String

6/10/2015 10:21:23 AM

Changes

Details

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
index 2d9c38b..1314afb 100755
--- 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
@@ -7,6 +7,13 @@
         <delete tableName="CLIENT_SESSION"/>
         <delete tableName="USER_SESSION_NOTE"/>
         <delete tableName="USER_SESSION"/>
+        <createTable tableName="DEFAULT_REQUIRED_ACTIONS">
+            <column name="REALM_ID" type="VARCHAR(36)">
+                <constraints nullable="false"/>
+            </column>
+            <column name="VALUE" type="VARCHAR(36)"/>
+        </createTable>
+
         <createTable tableName="ADMIN_EVENT_ENTITY">
             <column name="ID" type="VARCHAR(36)">
                 <constraints nullable="false"/>
@@ -145,6 +152,63 @@
             <column name="REQUIRED_ACTION" value="UPDATE_PASSWORD"/>
             <where>ACTION = 3</where>
         </update>
+
+        <addColumn tableName="CLIENT_SESSION">
+            <column name="CURRENT_ACTION" type="VARCHAR(36)">
+                <constraints nullable="false"/>
+            </column>
+        </addColumn>
+        <!-- OAUTH_GRANT,
+        CODE_TO_TOKEN,
+        VERIFY_EMAIL,
+        UPDATE_PROFILE,
+        CONFIGURE_TOTP,
+        UPDATE_PASSWORD,
+        RECOVER_PASSWORD,
+        AUTHENTICATE,
+        SOCIAL_CALLBACK,
+        LOGGED_OUT -->
+        <update tableName="CLIENT_SESSION">
+            <column name="CURRENT_ACTION" value="OAUTH_GRANT"/>
+            <where>ACTION = 0</where>
+        </update>
+        <update tableName="CLIENT_SESSION">
+            <column name="CURRENT_ACTION" value="CODE_TO_TOKEN"/>
+            <where>ACTION = 1</where>
+        </update>
+        <update tableName="CLIENT_SESSION">
+            <column name="CURRENT_ACTION" value="VERIFY_EMAIL"/>
+            <where>ACTION = 2</where>
+        </update>
+        <update tableName="CLIENT_SESSION">
+            <column name="CURRENT_ACTION" value="UPDATE_PROFILE"/>
+            <where>ACTION = 3</where>
+        </update>
+        <update tableName="CLIENT_SESSION">
+            <column name="CURRENT_ACTION" value="CONFIGURE_TOTP"/>
+            <where>ACTION = 4</where>
+        </update>
+        <update tableName="CLIENT_SESSION">
+            <column name="CURRENT_ACTION" value="UPDATE_PASSWORD"/>
+            <where>ACTION = 5</where>
+        </update>
+        <update tableName="CLIENT_SESSION">
+            <column name="CURRENT_ACTION" value="RECOVER_PASSWORD"/>
+            <where>ACTION = 6</where>
+        </update>
+        <update tableName="CLIENT_SESSION">
+            <column name="CURRENT_ACTION" value="AUTHENTICATE"/>
+            <where>ACTION = 7</where>
+        </update>
+        <update tableName="CLIENT_SESSION">
+            <column name="CURRENT_ACTION" value="SOCIAL_CALLBACK"/>
+            <where>ACTION = 8</where>
+        </update>
+        <update tableName="CLIENT_SESSION">
+            <column name="CURRENT_ACTION" value="LOGGED_OUT"/>
+            <where>ACTION = 9</where>
+        </update>
+
         <createTable tableName="CLIENT_USER_SESSION_NOTE">
             <column name="NAME" type="VARCHAR(255)">
                 <constraints nullable="false"/>
@@ -162,10 +226,12 @@
         <addPrimaryKey columnNames="AUTHENTICATOR_ID, NAME" constraintName="CONSTRAINT_AUTHENTICATOR_CONFIG_PK" tableName="AUTHENTICATOR_CONFIG"/>
         <dropPrimaryKey constraintName="CONSTRAINT_2" tableName="USER_REQUIRED_ACTION"/>
         <dropColumn tableName="USER_REQUIRED_ACTION" columnName="ACTION"/>
+        <dropColumn tableName="CLIENT_SESSION" 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"/>
         <addPrimaryKey columnNames="ID" constraintName="CONSTRAINT_FEDMAPPERPM" tableName="USER_FEDERATION_MAPPER"/>
         <addPrimaryKey columnNames="USER_FEDERATION_MAPPER_ID, NAME" constraintName="CONSTRAINT_FEDMAPPER_CFG_PM" tableName="USER_FEDERATION_MAPPER_CONFIG"/>
+        <addForeignKeyConstraint baseColumnNames="REALM_ID" baseTableName="DEFAULT_REQUIRED_ACTIONS" constraintName="FK_DEFAULT_REQUIRED_ACTIONS_REALM" referencedColumnNames="ID" referencedTableName="REALM"/>
         <addForeignKeyConstraint baseColumnNames="CLIENT_SESSION" baseTableName="CLIENT_SESSION_AUTH_STATUS" constraintName="AUTH_STATUS_CONSTRAINT" referencedColumnNames="ID" referencedTableName="CLIENT_SESSION"/>
         <addForeignKeyConstraint baseColumnNames="REALM_ID" baseTableName="AUTHENTICATOR" constraintName="FK_AUTHENTICATOR_REALM" referencedColumnNames="ID" referencedTableName="REALM"/>
         <addForeignKeyConstraint baseColumnNames="REALM_ID" baseTableName="AUTHENTICATION_FLOW" constraintName="FK_AUTHENTICATION_FLOW_REALM" referencedColumnNames="ID" referencedTableName="REALM"/>
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 5d68237..01f524a 100755
--- a/model/api/src/main/java/org/keycloak/models/ClientSessionModel.java
+++ b/model/api/src/main/java/org/keycloak/models/ClientSessionModel.java
@@ -22,9 +22,9 @@ public interface ClientSessionModel {
 
     public void setTimestamp(int timestamp);
 
-    public Action getAction();
+    public String getAction();
 
-    public void setAction(Action action);
+    public void setAction(String action);
 
     public Set<String> getRoles();
     public void setRoles(Set<String> roles);
diff --git a/model/api/src/main/java/org/keycloak/models/entities/RealmEntity.java b/model/api/src/main/java/org/keycloak/models/entities/RealmEntity.java
index 827c80b..b7f2588 100755
--- a/model/api/src/main/java/org/keycloak/models/entities/RealmEntity.java
+++ b/model/api/src/main/java/org/keycloak/models/entities/RealmEntity.java
@@ -2,8 +2,10 @@ package org.keycloak.models.entities;
 
 import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 
 /**
  * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
@@ -76,6 +78,7 @@ public class RealmEntity extends AbstractIdentifiableEntity {
     private List<IdentityProviderMapperEntity> identityProviderMappers = new ArrayList<IdentityProviderMapperEntity>();
     private List<AuthenticationFlowEntity> authenticationFlows = new ArrayList<>();
     private List<AuthenticatorEntity> authenticators = new ArrayList<>();
+    private List<String> defaultRequiredActions = new ArrayList<>();
 
 
     public String getName() {
@@ -500,6 +503,14 @@ public class RealmEntity extends AbstractIdentifiableEntity {
     public void setAuthenticators(List<AuthenticatorEntity> authenticators) {
         this.authenticators = authenticators;
     }
+
+    public List<String> getDefaultRequiredActions() {
+        return defaultRequiredActions;
+    }
+
+    public void setDefaultRequiredActions(List<String> defaultRequiredActions) {
+        this.defaultRequiredActions = defaultRequiredActions;
+    }
 }
 
 
diff --git a/model/api/src/main/java/org/keycloak/models/RealmModel.java b/model/api/src/main/java/org/keycloak/models/RealmModel.java
index 61bdbee..187c3fd 100755
--- a/model/api/src/main/java/org/keycloak/models/RealmModel.java
+++ b/model/api/src/main/java/org/keycloak/models/RealmModel.java
@@ -156,6 +156,13 @@ public interface RealmModel extends RoleContainerModel {
 
     void updateDefaultRoles(String[] defaultRoles);
 
+    Set<String> getDefaultRequiredActions();
+
+    void addDefaultRequiredAction(String action);
+    void removeDefaultRequiredAction(String action);
+
+    void setDefaultRequiredActions(Set<String> action);
+
     // Key is clientId
     Map<String, ClientModel> getClientNameMap();
 
diff --git a/model/api/src/main/java/org/keycloak/models/utils/DefaultAuthenticationFlows.java b/model/api/src/main/java/org/keycloak/models/utils/DefaultAuthenticationFlows.java
index af057b6..1f37bf1 100755
--- a/model/api/src/main/java/org/keycloak/models/utils/DefaultAuthenticationFlows.java
+++ b/model/api/src/main/java/org/keycloak/models/utils/DefaultAuthenticationFlows.java
@@ -104,5 +104,7 @@ public class DefaultAuthenticationFlows {
         execution.setAutheticatorFlow(false);
         realm.addAuthenticatorExecution(execution);
 
+        //
+
     }
 }
diff --git a/model/file/src/main/java/org/keycloak/models/file/adapter/RealmAdapter.java b/model/file/src/main/java/org/keycloak/models/file/adapter/RealmAdapter.java
index 1a19bad..bc2ff0a 100755
--- a/model/file/src/main/java/org/keycloak/models/file/adapter/RealmAdapter.java
+++ b/model/file/src/main/java/org/keycloak/models/file/adapter/RealmAdapter.java
@@ -1548,4 +1548,37 @@ public class RealmAdapter implements RealmModel {
         mapper.setConfig(config);
         return mapper;
     }
+
+    @Override
+    public Set<String> getDefaultRequiredActions() {
+        Set<String> result = new HashSet<String>();
+        if (realm.getDefaultRequiredActions() != null) {
+            result.addAll(realm.getDefaultRequiredActions());
+        }
+        return result;
+    }
+
+    @Override
+    public void addDefaultRequiredAction(String action) {
+        Set<String> actions = getDefaultRequiredActions();
+        actions.add(action);
+        setDefaultRequiredActions(actions);
+
+    }
+
+    @Override
+    public void removeDefaultRequiredAction(String action) {
+        Set<String> actions = getDefaultRequiredActions();
+        actions.remove(action);
+        setDefaultRequiredActions(actions);
+
+    }
+
+    @Override
+    public void setDefaultRequiredActions(Set<String> action) {
+        List<String> result = new ArrayList<String>();
+        result.addAll(action);
+        realm.setDefaultRequiredActions(result);
+
+    }
 }
diff --git a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedRealm.java b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedRealm.java
index 016870e..904081e 100755
--- a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedRealm.java
+++ b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedRealm.java
@@ -98,6 +98,7 @@ public class CachedRealm {
     private Set<String> supportedLocales = new HashSet<String>();
     private String defaultLocale;
     private MultivaluedHashMap<String, IdentityProviderMapperModel> identityProviderMappers = new MultivaluedHashMap<>();
+    private Set<String> defaultRequiredActions = new HashSet<>();
 
     public CachedRealm() {
     }
@@ -200,6 +201,7 @@ public class CachedRealm {
         for (AuthenticatorModel authenticator : model.getAuthenticators()) {
             authenticators.put(authenticator.getId(), authenticator);
         }
+        this.defaultRequiredActions.addAll(model.getDefaultRequiredActions());
 
     }
 
@@ -438,4 +440,8 @@ public class CachedRealm {
     public Map<String, AuthenticationExecutionModel> getExecutionsById() {
         return executionsById;
     }
+
+    public Set<String> getDefaultRequiredActions() {
+        return defaultRequiredActions;
+    }
 }
diff --git a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/RealmAdapter.java b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/RealmAdapter.java
index 75b8b82..db3339c 100755
--- a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/RealmAdapter.java
+++ b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/RealmAdapter.java
@@ -1116,4 +1116,30 @@ public class RealmAdapter implements RealmModel {
         if (updated != null) return updated.getAuthenticatorById(id);
         return cached.getAuthenticators().get(id);
     }
+
+    @Override
+    public Set<String> getDefaultRequiredActions() {
+        return cached.getDefaultRequiredActions();
+    }
+
+    @Override
+    public void addDefaultRequiredAction(String action) {
+        getDelegateForUpdate();
+        updated.addDefaultRequiredAction(action);
+
+    }
+
+    @Override
+    public void removeDefaultRequiredAction(String action) {
+        getDelegateForUpdate();
+        updated.removeDefaultRequiredAction(action);
+
+    }
+
+    @Override
+    public void setDefaultRequiredActions(Set<String> action) {
+        getDelegateForUpdate();
+        updated.setDefaultRequiredActions(action);
+
+    }
 }
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java
index 41be2b1..5ccb017 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java
@@ -114,6 +114,12 @@ public class RealmEntity {
     Collection<RoleEntity> roles = new ArrayList<RoleEntity>();
 
     @ElementCollection
+    @Column(name="VALUE")
+    @CollectionTable(name = "DEFAULT_REQUIRED_ACTIONS", joinColumns={ @JoinColumn(name="REALM_ID") })
+    protected Set<String> defaultRequiredActions = new HashSet<String>();
+
+
+    @ElementCollection
     @MapKeyColumn(name="NAME")
     @Column(name="VALUE")
     @CollectionTable(name="REALM_SMTP_CONFIG", joinColumns={ @JoinColumn(name="REALM_ID") })
@@ -568,5 +574,13 @@ public class RealmEntity {
     public void setAuthenticationFlows(Collection<AuthenticationFlowEntity> authenticationFlows) {
         this.authenticationFlows = authenticationFlows;
     }
+
+    public Set<String> getDefaultRequiredActions() {
+        return defaultRequiredActions;
+    }
+
+    public void setDefaultRequiredActions(Set<String> defaultRequiredActions) {
+        this.defaultRequiredActions = defaultRequiredActions;
+    }
 }
 
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
index 7db1b47..f2f1929 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
@@ -1707,5 +1707,30 @@ public class RealmAdapter implements RealmModel {
         return authenticators;
     }
 
+    @Override
+    public Set<String> getDefaultRequiredActions() {
+        Set<String> result = new HashSet<String>();
+        result.addAll(realm.getDefaultRequiredActions());
+        return result;
+    }
+
+
+
+    @Override
+    public void setDefaultRequiredActions(Set<String> actions) {
+        realm.setDefaultRequiredActions(actions);
+    }
+
+    @Override
+    public void addDefaultRequiredAction(String action) {
+        realm.getDefaultRequiredActions().add(action);
+    }
+
+    @Override
+    public void removeDefaultRequiredAction(String action) {
+        realm.getDefaultRequiredActions().remove(action);
+    }
+
+
 
 }
\ No newline at end of file
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java
index aa25be7..24d907c 100755
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java
@@ -1590,4 +1590,32 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
         mapper.setConfig(config);
         return mapper;
     }
+
+    @Override
+    public Set<String> getDefaultRequiredActions() {
+        Set<String> result = new HashSet<String>();
+        result.addAll(realm.getDefaultRequiredActions());
+        return result;
+    }
+
+
+
+    @Override
+    public void setDefaultRequiredActions(Set<String> actions) {
+        List<String> result = new ArrayList<String>();
+        result.addAll(actions);
+        getMongoEntity().setDefaultRequiredActions(result);
+        updateMongoEntity();
+    }
+
+    @Override
+    public void addDefaultRequiredAction(String action) {
+        getMongoStore().pushItemToList(getMongoEntity(), "defaultRequiredActions", action, true, invocationContext);
+    }
+
+    @Override
+    public void removeDefaultRequiredAction(String action) {
+        getMongoStore().pullItemFromList(getMongoEntity(), "defaultRequiredActions", action, invocationContext);
+    }
+
 }
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 226a2ec..78be9ee 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
@@ -100,12 +100,12 @@ public class ClientSessionAdapter implements ClientSessionModel {
     }
 
     @Override
-    public Action getAction() {
+    public String getAction() {
         return entity.getAction();
     }
 
     @Override
-    public void setAction(Action action) {
+    public void setAction(String action) {
         entity.setAction(action);
         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 53c8218..340bf92 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
@@ -24,7 +24,7 @@ public class ClientSessionEntity extends SessionEntity {
 
     private int timestamp;
 
-    private ClientSessionModel.Action action;
+    private String action;
 
     private Set<String> roles;
     private Set<String> protocolMappers;
@@ -81,11 +81,11 @@ public class ClientSessionEntity extends SessionEntity {
         this.timestamp = timestamp;
     }
 
-    public ClientSessionModel.Action getAction() {
+    public String getAction() {
         return action;
     }
 
-    public void setAction(ClientSessionModel.Action action) {
+    public void setAction(String action) {
         this.action = action;
     }
 
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 0ce21fa..2ce7034 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
@@ -189,12 +189,12 @@ public class ClientSessionAdapter implements ClientSessionModel {
     }
 
     @Override
-    public Action getAction() {
+    public String getAction() {
         return entity.getAction();
     }
 
     @Override
-    public void setAction(Action action) {
+    public void setAction(String action) {
         entity.setAction(action);
     }
 
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 bf0c520..300c45a 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
@@ -54,8 +54,8 @@ public class ClientSessionEntity {
     @Column(name="AUTH_METHOD")
     protected String authMethod;
 
-    @Column(name="ACTION")
-    protected ClientSessionModel.Action action;
+    @Column(name="CURRENT_ACTION")
+    protected String action;
 
     @Column(name="AUTH_USER_ID")
     protected String userId;
@@ -123,11 +123,11 @@ public class ClientSessionEntity {
         this.redirectUri = redirectUri;
     }
 
-    public ClientSessionModel.Action getAction() {
+    public String getAction() {
         return action;
     }
 
-    public void setAction(ClientSessionModel.Action action) {
+    public void setAction(String action) {
         this.action = action;
     }
 
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 cbea1d6..0e0647f 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
@@ -93,12 +93,12 @@ public class ClientSessionAdapter implements ClientSessionModel {
     }
 
     @Override
-    public ClientSessionModel.Action getAction() {
+    public String getAction() {
         return entity.getAction();
     }
 
     @Override
-    public void setAction(ClientSessionModel.Action action) {
+    public void setAction(String action) {
         entity.setAction(action);
     }
 
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 bd6eac9..e76f624 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
@@ -24,7 +24,7 @@ public class ClientSessionEntity {
     private String authMethod;
 
     private int timestamp;
-    private ClientSessionModel.Action action;
+    private String action;
     private Set<String> roles;
     private Set<String> protocolMappers;
     private Map<String, String> notes = new HashMap<>();
@@ -78,11 +78,11 @@ public class ClientSessionEntity {
         this.timestamp = timestamp;
     }
 
-    public ClientSessionModel.Action getAction() {
+    public String getAction() {
         return action;
     }
 
-    public void setAction(ClientSessionModel.Action action) {
+    public void setAction(String action) {
         this.action = action;
     }
 
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 52fa0aa..ad1d0d7 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
@@ -108,12 +108,12 @@ public class ClientSessionAdapter extends AbstractMongoAdapter<MongoClientSessio
     }
 
     @Override
-    public Action getAction() {
+    public String getAction() {
         return entity.getAction();
     }
 
     @Override
-    public void setAction(Action action) {
+    public void setAction(String action) {
         entity.setAction(action);
         updateMongoEntity();
     }
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 10a211f..21831b6 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
@@ -26,7 +26,7 @@ public class MongoClientSessionEntity extends AbstractIdentifiableEntity impleme
     private String authMethod;
 
     private int timestamp;
-    private ClientSessionModel.Action action;
+    private String action;
     private List<String> roles;
     private List<String> protocolMappers;
     private Map<String, String> notes = new HashMap<String, String>();
@@ -82,11 +82,11 @@ public class MongoClientSessionEntity extends AbstractIdentifiableEntity impleme
         this.timestamp = timestamp;
     }
 
-    public ClientSessionModel.Action getAction() {
+    public String getAction() {
         return action;
     }
 
-    public void setAction(ClientSessionModel.Action action) {
+    public void setAction(String action) {
         this.action = action;
     }
 
diff --git a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlService.java b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlService.java
index 246e115..69dcbef 100755
--- a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlService.java
+++ b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlService.java
@@ -21,6 +21,7 @@ import org.keycloak.login.LoginFormsProvider;
 import org.keycloak.models.AuthenticationFlowModel;
 import org.keycloak.models.ClientModel;
 import org.keycloak.models.ClientSessionModel;
+import org.keycloak.models.IdentityProviderModel;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.UserSessionModel;
@@ -32,6 +33,7 @@ import org.keycloak.saml.common.exceptions.ConfigurationException;
 import org.keycloak.saml.common.exceptions.ProcessingException;
 import org.keycloak.saml.processing.core.saml.v2.common.SAMLDocumentHolder;
 import org.keycloak.services.ErrorPage;
+import org.keycloak.services.Urls;
 import org.keycloak.services.managers.AuthenticationManager;
 import org.keycloak.services.managers.ClientSessionCode;
 import org.keycloak.services.managers.HttpAuthenticationManager;
@@ -59,6 +61,7 @@ import java.io.IOException;
 import java.io.InputStream;
 import java.net.URI;
 import java.security.PublicKey;
+import java.util.List;
 
 /**
  * Resource class for the oauth/openid connect token service
@@ -264,7 +267,7 @@ public class SamlService {
             ClientSessionModel clientSession = session.sessions().createClientSession(realm, client);
             clientSession.setAuthMethod(SamlProtocol.LOGIN_PROTOCOL);
             clientSession.setRedirectUri(redirect);
-            clientSession.setAction(ClientSessionModel.Action.AUTHENTICATE);
+            clientSession.setAction(ClientSessionModel.Action.AUTHENTICATE.name());
             clientSession.setNote(ClientSessionCode.ACTION_KEY, KeycloakModelUtils.generateCodeSecret());
             clientSession.setNote(SamlProtocol.SAML_BINDING, bindingType);
             clientSession.setNote(GeneralConstants.RELAY_STATE, relayState);
@@ -317,7 +320,22 @@ public class SamlService {
             return forms.createLogin();
         }
 
+        private Response buildRedirectToIdentityProvider(String providerId, String accessCode) {
+            logger.debug("Automatically redirect to identity provider: " + providerId);
+            return Response.temporaryRedirect(
+                    Urls.identityProviderAuthnRequest(uriInfo.getBaseUri(), providerId, realm.getName(), accessCode))
+                    .build();
+        }
+
+
         protected Response newBrowserAuthentication(ClientSessionModel clientSession) {
+            List<IdentityProviderModel> identityProviders = realm.getIdentityProviders();
+            for (IdentityProviderModel identityProvider : identityProviders) {
+                if (identityProvider.isAuthenticateByDefault()) {
+                    return buildRedirectToIdentityProvider(identityProvider.getAlias(), new ClientSessionCode(realm, clientSession).getCode() );
+                }
+            }
+
             String flowId = null;
             for (AuthenticationFlowModel flow : realm.getAuthenticationFlows()) {
                 if (flow.getAlias().equals("browser")) {
@@ -336,7 +354,11 @@ public class SamlService {
                     .setUriInfo(uriInfo)
                     .setRequest(request);
 
-            return processor.authenticate();
+            try {
+                return processor.authenticate();
+            } catch (Exception e) {
+                return processor.handleBrowserException(e);
+            }
         }
 
 
@@ -394,7 +416,7 @@ public class SamlService {
                 // remove client from logout requests
                 for (ClientSessionModel clientSession : userSession.getClientSessions()) {
                     if (clientSession.getClient().getId().equals(client.getId())) {
-                        clientSession.setAction(ClientSessionModel.Action.LOGGED_OUT);
+                        clientSession.setAction(ClientSessionModel.Action.LOGGED_OUT.name());
                     }
                 }
                 logger.debug("browser Logout");
@@ -405,7 +427,7 @@ public class SamlService {
                     if (clientSession == null) continue;
                     if (clientSession.getClient().getClientId().equals(client.getClientId())) {
                         // remove requesting client from logout
-                        clientSession.setAction(ClientSessionModel.Action.LOGGED_OUT);
+                        clientSession.setAction(ClientSessionModel.Action.LOGGED_OUT.name());
                     }
                     UserSessionModel userSession = clientSession.getUserSession();
                     try {
diff --git a/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java b/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java
index c192a04..7d3760e 100755
--- a/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java
+++ b/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java
@@ -4,6 +4,7 @@ import org.jboss.logging.Logger;
 import org.jboss.resteasy.spi.HttpRequest;
 import org.keycloak.ClientConnection;
 import org.keycloak.events.Details;
+import org.keycloak.events.Errors;
 import org.keycloak.events.EventBuilder;
 import org.keycloak.events.EventType;
 import org.keycloak.models.AuthenticationExecutionModel;
@@ -15,8 +16,10 @@ 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.ErrorPage;
 import org.keycloak.services.managers.AuthenticationManager;
 import org.keycloak.services.managers.BruteForceProtector;
+import org.keycloak.services.messages.Messages;
 
 import javax.ws.rs.core.Response;
 import javax.ws.rs.core.UriInfo;
@@ -39,6 +42,7 @@ public class AuthenticationProcessor {
     protected EventBuilder event;
     protected HttpRequest request;
     protected String flowId;
+    protected String action;
     protected boolean userSessionCreated;
 
 
@@ -134,6 +138,11 @@ public class AuthenticationProcessor {
         return this;
     }
 
+    public AuthenticationProcessor setAction(String action) {
+        this.action = action;
+        return this;
+    }
+
     private class Result implements AuthenticatorContext {
         AuthenticatorModel model;
         AuthenticationExecutionModel execution;
@@ -169,6 +178,11 @@ public class AuthenticationProcessor {
         }
 
         @Override
+        public String getAction() {
+            return AuthenticationProcessor.this.action;
+        }
+
+        @Override
         public Authenticator getAuthenticator() {
             return authenticator;
         }
@@ -332,6 +346,34 @@ public class AuthenticationProcessor {
         return status == UserSessionModel.AuthenticatorStatus.SUCCESS;
     }
 
+    public Response handleBrowserException(Exception failure) {
+        if (failure instanceof AuthException) {
+            AuthException e = (AuthException)failure;
+            logger.error("failed authentication: " + e.getError().toString(), e);
+            if (e.getError() == AuthenticationProcessor.Error.INVALID_USER) {
+                event.error(Errors.USER_NOT_FOUND);
+                return ErrorPage.error(session, Messages.INVALID_USER);
+            } else if (e.getError() == AuthenticationProcessor.Error.USER_DISABLED) {
+                event.error(Errors.USER_DISABLED);
+                return ErrorPage.error(session, Messages.ACCOUNT_DISABLED);
+            } else if (e.getError() == AuthenticationProcessor.Error.USER_TEMPORARILY_DISABLED) {
+                event.error(Errors.USER_TEMPORARILY_DISABLED);
+                return ErrorPage.error(session, Messages.ACCOUNT_TEMPORARILY_DISABLED);
+
+            } else {
+                event.error(Errors.INVALID_USER_CREDENTIALS);
+                return ErrorPage.error(session, Messages.INVALID_USER);
+            }
+
+        } else {
+            logger.error("failed authentication", failure);
+            event.error(Errors.INVALID_USER_CREDENTIALS);
+            return ErrorPage.error(session, Messages.UNEXPECTED_ERROR_HANDLING_REQUEST);
+        }
+
+    }
+
+
     public Response authenticate() throws AuthException {
         logger.debug("AUTHENTICATE");
         event.event(EventType.LOGIN);
diff --git a/services/src/main/java/org/keycloak/authentication/AuthenticatorContext.java b/services/src/main/java/org/keycloak/authentication/AuthenticatorContext.java
index 91e9514..1569c03 100755
--- a/services/src/main/java/org/keycloak/authentication/AuthenticatorContext.java
+++ b/services/src/main/java/org/keycloak/authentication/AuthenticatorContext.java
@@ -31,6 +31,8 @@ public interface AuthenticatorContext {
 
     void setAuthenticatorModel(AuthenticatorModel model);
 
+    String getAction();
+
     Authenticator getAuthenticator();
 
     void setAuthenticator(Authenticator authenticator);
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 20099f4..22bba0e 100755
--- a/services/src/main/java/org/keycloak/authentication/authenticators/AbstractFormAuthenticator.java
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/AbstractFormAuthenticator.java
@@ -19,26 +19,27 @@ import java.net.URI;
  * @version $Revision: 1 $
  */
 public class AbstractFormAuthenticator {
-    protected boolean isActionUrl(AuthenticatorContext context) {
-        URI expected = LoginActionsService.authenticationFormProcessor(context.getUriInfo()).build(context.getRealm().getName());
-        String current = context.getUriInfo().getAbsolutePath().getPath();
-        String expectedPath = expected.getPath();
-        return expectedPath.equals(current);
 
+    public static final String LOGIN_FORM_ACTION = "login_form";
+    public static final String ACTION = "action";
+
+    protected boolean isAction(AuthenticatorContext context, String action) {
+        return action.equals(context.getAction());
     }
 
     protected LoginFormsProvider loginForm(AuthenticatorContext context) {
         ClientSessionCode code = new ClientSessionCode(context.getRealm(), context.getClientSession());
-        code.setAction(ClientSessionModel.Action.AUTHENTICATE);
-        URI action = getActionUrl(context, code);
+        code.setAction(ClientSessionModel.Action.AUTHENTICATE.name());
+        URI action = getActionUrl(context, code, LOGIN_FORM_ACTION);
         return context.getSession().getProvider(LoginFormsProvider.class)
                     .setActionUri(action)
                     .setClientSessionCode(code.getCode());
     }
 
-    public static URI getActionUrl(AuthenticatorContext context, ClientSessionCode code) {
+    public static URI getActionUrl(AuthenticatorContext context, ClientSessionCode code, String action) {
         return LoginActionsService.authenticationFormProcessor(context.getUriInfo())
-                    .queryParam(OAuth2Constants.CODE, code.getCode())
+                .queryParam(OAuth2Constants.CODE, code.getCode())
+                .queryParam(ACTION, action)
                     .build(context.getRealm().getName());
     }
 
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/LoginFormOTPAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/LoginFormOTPAuthenticator.java
index a750158..bac37f7 100755
--- a/services/src/main/java/org/keycloak/authentication/authenticators/LoginFormOTPAuthenticator.java
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/LoginFormOTPAuthenticator.java
@@ -27,7 +27,7 @@ public class LoginFormOTPAuthenticator extends LoginFormUsernameAuthenticator {
 
     @Override
     public void authenticate(AuthenticatorContext context) {
-        if (!isActionUrl(context)) {
+        if (!isAction(context, LOGIN_FORM_ACTION)) {
             context.failure(AuthenticationProcessor.Error.INTERNAL_ERROR);
             return;
         }
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/LoginFormPasswordAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/LoginFormPasswordAuthenticator.java
index 3995b3a..680be06 100755
--- a/services/src/main/java/org/keycloak/authentication/authenticators/LoginFormPasswordAuthenticator.java
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/LoginFormPasswordAuthenticator.java
@@ -9,7 +9,6 @@ import org.keycloak.models.RealmModel;
 import org.keycloak.models.UserCredentialModel;
 import org.keycloak.models.UserModel;
 import org.keycloak.representations.idm.CredentialRepresentation;
-import org.keycloak.services.messages.Messages;
 
 import javax.ws.rs.core.MultivaluedMap;
 import javax.ws.rs.core.Response;
@@ -28,7 +27,7 @@ public class LoginFormPasswordAuthenticator extends LoginFormUsernameAuthenticat
 
     @Override
     public void authenticate(AuthenticatorContext context) {
-        if (!isActionUrl(context)) {
+        if (!isAction(context, LOGIN_FORM_ACTION)) {
             context.failure(AuthenticationProcessor.Error.INTERNAL_ERROR);
             return;
         }
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/LoginFormUsernameAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/LoginFormUsernameAuthenticator.java
index 6550d13..7904470 100755
--- a/services/src/main/java/org/keycloak/authentication/authenticators/LoginFormUsernameAuthenticator.java
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/LoginFormUsernameAuthenticator.java
@@ -33,7 +33,7 @@ public class LoginFormUsernameAuthenticator extends AbstractFormAuthenticator im
 
     @Override
     public void authenticate(AuthenticatorContext context) {
-        if (!isActionUrl(context)) {
+        if (!isAction(context, LOGIN_FORM_ACTION)) {
             MultivaluedMap<String, String> formData = new MultivaluedMapImpl<>();
             String loginHint = context.getClientSession().getNote(OIDCLoginProtocol.LOGIN_HINT_PARAM);
 
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 cf0d4fa..325f3cd 100755
--- a/services/src/main/java/org/keycloak/authentication/authenticators/OTPFormAuthenticator.java
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/OTPFormAuthenticator.java
@@ -13,7 +13,6 @@ import org.keycloak.models.UserModel;
 import org.keycloak.representations.idm.CredentialRepresentation;
 import org.keycloak.services.managers.ClientSessionCode;
 import org.keycloak.services.messages.Messages;
-import org.keycloak.services.resources.LoginActionsService;
 
 import javax.ws.rs.core.MultivaluedMap;
 import javax.ws.rs.core.Response;
@@ -26,6 +25,7 @@ import java.util.List;
  * @version $Revision: 1 $
  */
 public class OTPFormAuthenticator extends AbstractFormAuthenticator implements Authenticator {
+    public static final String TOTP_FORM_ACTION = "totp";
     protected AuthenticatorModel model;
 
     public OTPFormAuthenticator(AuthenticatorModel model) {
@@ -34,7 +34,7 @@ public class OTPFormAuthenticator extends AbstractFormAuthenticator implements A
 
     @Override
     public void authenticate(AuthenticatorContext context) {
-        if (!isActionUrl(context)) {
+        if (!isAction(context, TOTP_FORM_ACTION)) {
             Response challengeResponse = challenge(context, null);
             context.challenge(challengeResponse);
             return;
@@ -43,7 +43,7 @@ public class OTPFormAuthenticator extends AbstractFormAuthenticator implements A
     }
 
     public void validateOTP(AuthenticatorContext context) {
-        MultivaluedMap<String, String> inputData = context.getHttpRequest().getFormParameters();
+        MultivaluedMap<String, String> inputData = context.getHttpRequest().getDecodedFormParameters();
         List<UserCredentialModel> credentials = new LinkedList<>();
         String password = inputData.getFirst(CredentialRepresentation.TOTP);
         if (password == null) {
@@ -70,7 +70,7 @@ public class OTPFormAuthenticator extends AbstractFormAuthenticator implements A
 
     protected Response challenge(AuthenticatorContext context, String error) {
         ClientSessionCode clientSessionCode = new ClientSessionCode(context.getRealm(), context.getClientSession());
-        URI action = AbstractFormAuthenticator.getActionUrl(context, clientSessionCode);
+        URI action = AbstractFormAuthenticator.getActionUrl(context, clientSessionCode, TOTP_FORM_ACTION);
         LoginFormsProvider forms = context.getSession().getProvider(LoginFormsProvider.class)
                 .setActionUri(action)
                 .setClientSessionCode(clientSessionCode.getCode());
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/SetRequiredActionAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/SetRequiredActionAuthenticator.java
new file mode 100755
index 0000000..563d8f2
--- /dev/null
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/SetRequiredActionAuthenticator.java
@@ -0,0 +1,48 @@
+package org.keycloak.authentication.authenticators;
+
+import org.keycloak.authentication.AuthenticationProcessor;
+import org.keycloak.authentication.Authenticator;
+import org.keycloak.authentication.AuthenticatorContext;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.services.managers.AuthenticationManager;
+
+/**
+ * No auth, but it sets a required action.
+ *
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class SetRequiredActionAuthenticator implements Authenticator {
+
+    @Override
+    public boolean requiresUser() {
+        return false;
+    }
+
+    @Override
+    public void authenticate(AuthenticatorContext context) {
+        UserModel user = context.getUser();
+        if (user == null) {
+            throw new AuthenticationProcessor.AuthException(AuthenticationProcessor.Error.UNKNOWN_USER);
+        }
+        user.addRequiredAction(context.getAuthenticatorModel().getConfig().get("required.action"));
+        context.success();
+    }
+
+    @Override
+    public boolean configuredFor(KeycloakSession session, RealmModel realm, 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/SetRequiredActionAuthenticatorFactory.java b/services/src/main/java/org/keycloak/authentication/authenticators/SetRequiredActionAuthenticatorFactory.java
new file mode 100755
index 0000000..79f9f55
--- /dev/null
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/SetRequiredActionAuthenticatorFactory.java
@@ -0,0 +1,69 @@
+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;
+import org.keycloak.provider.ProviderConfigProperty;
+
+import java.util.List;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class SetRequiredActionAuthenticatorFactory implements AuthenticatorFactory {
+    public static final String PROVIDER_ID = "auth-set-required-action";
+    static SetRequiredActionAuthenticator SINGLETON = new SetRequiredActionAuthenticator();
+    @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 PROVIDER_ID;
+    }
+
+    @Override
+    public String getDisplayCategory() {
+        return "Action";
+    }
+
+    @Override
+    public String getDisplayType() {
+        return "Set Required Action";
+    }
+
+    @Override
+    public String getHelpText() {
+        return "Doesn't do any authentication.  Instead it just sets a configured required action for the user.";
+    }
+
+    @Override
+    public List<ProviderConfigProperty> getConfigProperties() {
+        return null;
+    }
+}
diff --git a/services/src/main/java/org/keycloak/authentication/RequiredActionContext.java b/services/src/main/java/org/keycloak/authentication/RequiredActionContext.java
new file mode 100755
index 0000000..62d4a93
--- /dev/null
+++ b/services/src/main/java/org/keycloak/authentication/RequiredActionContext.java
@@ -0,0 +1,32 @@
+package org.keycloak.authentication;
+
+import org.jboss.resteasy.spi.HttpRequest;
+import org.keycloak.ClientConnection;
+import org.keycloak.events.EventBuilder;
+import org.keycloak.models.AuthenticationExecutionModel;
+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 RequiredActionContext {
+    EventBuilder getEvent();
+    UserModel getUser();
+    RealmModel getRealm();
+    ClientSessionModel getClientSession();
+    UserSessionModel getUserSession();
+    ClientConnection getConnection();
+    UriInfo getUriInfo();
+    KeycloakSession getSession();
+    HttpRequest getHttpRequest();
+}
diff --git a/services/src/main/java/org/keycloak/authentication/RequiredActionProvider.java b/services/src/main/java/org/keycloak/authentication/RequiredActionProvider.java
new file mode 100755
index 0000000..b5cc21a
--- /dev/null
+++ b/services/src/main/java/org/keycloak/authentication/RequiredActionProvider.java
@@ -0,0 +1,14 @@
+package org.keycloak.authentication;
+
+import org.keycloak.provider.Provider;
+
+import javax.ws.rs.core.Response;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public interface RequiredActionProvider extends Provider {
+    Response invokeRequiredAction(RequiredActionContext context);
+    Object jaxrsService();
+}
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/AuthorizationEndpoint.java b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/AuthorizationEndpoint.java
index a5baa07..3c66dbe 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/AuthorizationEndpoint.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/AuthorizationEndpoint.java
@@ -220,7 +220,7 @@ public class AuthorizationEndpoint {
         clientSession = session.sessions().createClientSession(realm, client);
         clientSession.setAuthMethod(OIDCLoginProtocol.LOGIN_PROTOCOL);
         clientSession.setRedirectUri(redirectUri);
-        clientSession.setAction(ClientSessionModel.Action.AUTHENTICATE);
+        clientSession.setAction(ClientSessionModel.Action.AUTHENTICATE.name());
         clientSession.setNote(ClientSessionCode.ACTION_KEY, KeycloakModelUtils.generateCodeSecret());
         clientSession.setNote(OIDCLoginProtocol.RESPONSE_TYPE_PARAM, responseType);
         clientSession.setNote(OIDCLoginProtocol.REDIRECT_URI_PARAM, redirectUriParam);
@@ -277,7 +277,12 @@ public class AuthorizationEndpoint {
                 .setUriInfo(uriInfo)
                 .setRequest(request);
 
-        Response challenge = processor.authenticateOnly();
+        Response challenge = null;
+        try {
+            challenge = processor.authenticateOnly();
+        } catch (Exception e) {
+            return processor.handleBrowserException(e);
+        }
 
         if (challenge != null && prompt != null && prompt.equals("none")) {
             if (processor.isUserSessionCreated()) {
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java
index 53c74c9..7582dcd 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java
@@ -191,7 +191,7 @@ public class TokenEndpoint {
 
         ClientSessionModel clientSession = accessCode.getClientSession();
         event.detail(Details.CODE_ID, clientSession.getId());
-        if (!accessCode.isValid(ClientSessionModel.Action.CODE_TO_TOKEN)) {
+        if (!accessCode.isValid(ClientSessionModel.Action.CODE_TO_TOKEN.name())) {
             event.error(Errors.INVALID_CODE);
             throw new ErrorResponseException("invalid_grant", "Code is expired", Response.Status.BAD_REQUEST);
         }
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocol.java b/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocol.java
index b7c7381..4ef8544 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocol.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocol.java
@@ -130,7 +130,7 @@ public class OIDCLoginProtocol implements LoginProtocol {
         ClientSessionModel clientSession = accessCode.getClientSession();
         String redirect = clientSession.getRedirectUri();
         String state = clientSession.getNote(OIDCLoginProtocol.STATE_PARAM);
-        accessCode.setAction(ClientSessionModel.Action.CODE_TO_TOKEN);
+        accessCode.setAction(ClientSessionModel.Action.CODE_TO_TOKEN.name());
         UriBuilder redirectUri = UriBuilder.fromUri(redirect).queryParam(OAuth2Constants.CODE, accessCode.getCode());
         log.debugv("redirectAccessCode: state: {0}", state);
         if (state != null)
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 6b12a43..f445bfe 100755
--- a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
@@ -148,7 +148,7 @@ public class AuthenticationManager {
 
     public static void backchannelLogoutClientSession(KeycloakSession session, RealmModel realm, ClientSessionModel clientSession, UserSessionModel userSession, UriInfo uriInfo, HttpHeaders headers) {
         ClientModel client = clientSession.getClient();
-        if (client instanceof ClientModel && !client.isFrontchannelLogout() && clientSession.getAction() != ClientSessionModel.Action.LOGGED_OUT) {
+        if (client instanceof ClientModel && !client.isFrontchannelLogout() && !ClientSessionModel.Action.LOGGED_OUT.name().equals(clientSession.getAction())) {
             String authMethod = clientSession.getAuthMethod();
             if (authMethod == null) return; // must be a keycloak service like account
             LoginProtocol protocol = session.getProvider(LoginProtocol.class, authMethod);
@@ -156,7 +156,7 @@ public class AuthenticationManager {
                     .setHttpHeaders(headers)
                     .setUriInfo(uriInfo);
             protocol.backchannelLogout(userSession, clientSession);
-            clientSession.setAction(ClientSessionModel.Action.LOGGED_OUT);
+            clientSession.setAction(ClientSessionModel.Action.LOGGED_OUT.name());
         }
 
     }
@@ -188,7 +188,7 @@ public class AuthenticationManager {
         List<ClientSessionModel> redirectClients = new LinkedList<ClientSessionModel>();
         for (ClientSessionModel clientSession : userSession.getClientSessions()) {
             ClientModel client = clientSession.getClient();
-            if (clientSession.getAction() == ClientSessionModel.Action.LOGGED_OUT) continue;
+            if (ClientSessionModel.Action.LOGGED_OUT.name().equals(clientSession.getAction())) continue;
             if (client.isFrontchannelLogout()) {
                 String authMethod = clientSession.getAuthMethod();
                 if (authMethod == null) continue; // must be a keycloak service like account
@@ -205,7 +205,7 @@ public class AuthenticationManager {
                 try {
                     logger.debugv("backchannel logout to: {0}", client.getClientId());
                     protocol.backchannelLogout(userSession, clientSession);
-                    clientSession.setAction(ClientSessionModel.Action.LOGGED_OUT);
+                    clientSession.setAction(ClientSessionModel.Action.LOGGED_OUT.name());
                 } catch (Exception e) {
                     logger.warn("Failed to logout client, continuing", e);
                 }
@@ -219,7 +219,7 @@ public class AuthenticationManager {
                     .setHttpHeaders(headers)
                     .setUriInfo(uriInfo);
             // setting this to logged out cuz I"m not sure protocols can always verify that the client was logged out or not
-            nextRedirectClient.setAction(ClientSessionModel.Action.LOGGED_OUT);
+            nextRedirectClient.setAction(ClientSessionModel.Action.LOGGED_OUT.name());
             try {
                 logger.debugv("frontchannel logout to: {0}", nextRedirectClient.getClient().getClientId());
                 Response response = protocol.frontchannelLogout(userSession, nextRedirectClient);
@@ -476,7 +476,7 @@ public class AuthenticationManager {
         }
 
         if (client.isConsentRequired()) {
-            accessCode.setAction(ClientSessionModel.Action.OAUTH_GRANT);
+            accessCode.setAction(ClientSessionModel.Action.OAUTH_GRANT.name());
 
             UserConsentModel grantedConsent = user.getConsentByClient(client.getId());
 
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 99fc5f2..8ddc02f 100755
--- a/services/src/main/java/org/keycloak/services/managers/ClientSessionCode.java
+++ b/services/src/main/java/org/keycloak/services/managers/ClientSessionCode.java
@@ -80,8 +80,8 @@ public class ClientSessionCode {
         return clientSession;
     }
 
-    public boolean isValid(ClientSessionModel.Action requestedAction) {
-        ClientSessionModel.Action action = clientSession.getAction();
+    public boolean isValid(String requestedAction) {
+        String action = clientSession.getAction();
         if (action == null) {
             return false;
         }
@@ -93,18 +93,14 @@ public class ClientSessionCode {
         }
 
         int lifespan;
-        switch (action) {
-            case CODE_TO_TOKEN:
-                lifespan = realm.getAccessCodeLifespan();
-                break;
-            case AUTHENTICATE:
-                lifespan = realm.getAccessCodeLifespanLogin() > 0 ? realm.getAccessCodeLifespanLogin() : realm.getAccessCodeLifespanUserAction();
-                break;
-            default:
-                lifespan = realm.getAccessCodeLifespanUserAction();
-                break;
-        }
+        if (action.equals(ClientSessionModel.Action.CODE_TO_TOKEN.name())) {
+            lifespan = realm.getAccessCodeLifespan();
 
+        } else if (action.equals(ClientSessionModel.Action.AUTHENTICATE.name())) {
+            lifespan = realm.getAccessCodeLifespanLogin() > 0 ? realm.getAccessCodeLifespanLogin() : realm.getAccessCodeLifespanUserAction();
+        } else {
+            lifespan = realm.getAccessCodeLifespanUserAction();
+        }
         return timestamp + lifespan > Time.currentTime();
     }
 
@@ -132,7 +128,7 @@ public class ClientSessionCode {
         return requestedProtocolMappers;
     }
 
-    public void setAction(ClientSessionModel.Action action) {
+    public void setAction(String action) {
         clientSession.setAction(action);
         clientSession.setNote(ACTION_KEY, UUID.randomUUID().toString());
         clientSession.setTimestamp(Time.currentTime());
@@ -142,16 +138,16 @@ public class ClientSessionCode {
         setAction(convertToAction(requiredAction));
     }
 
-    private ClientSessionModel.Action convertToAction(RequiredAction requiredAction) {
+    private String convertToAction(RequiredAction requiredAction) {
         switch (requiredAction) {
             case CONFIGURE_TOTP:
-                return ClientSessionModel.Action.CONFIGURE_TOTP;
+                return ClientSessionModel.Action.CONFIGURE_TOTP.name();
             case UPDATE_PASSWORD:
-                return ClientSessionModel.Action.UPDATE_PASSWORD;
+                return ClientSessionModel.Action.UPDATE_PASSWORD.name();
             case UPDATE_PROFILE:
-                return ClientSessionModel.Action.UPDATE_PROFILE;
+                return ClientSessionModel.Action.UPDATE_PROFILE.name();
             case VERIFY_EMAIL:
-                return ClientSessionModel.Action.VERIFY_EMAIL;
+                return ClientSessionModel.Action.VERIFY_EMAIL.name();
             default:
                 throw new IllegalArgumentException("Unknown required action " + requiredAction);
         }
diff --git a/services/src/main/java/org/keycloak/services/resources/AccountService.java b/services/src/main/java/org/keycloak/services/resources/AccountService.java
index 697a28f..4ceebb3 100755
--- a/services/src/main/java/org/keycloak/services/resources/AccountService.java
+++ b/services/src/main/java/org/keycloak/services/resources/AccountService.java
@@ -738,7 +738,7 @@ public class AccountService {
                 try {
                     ClientSessionModel clientSession = auth.getClientSession();
                     ClientSessionCode clientSessionCode = new ClientSessionCode(realm, clientSession);
-                    clientSessionCode.setAction(ClientSessionModel.Action.AUTHENTICATE);
+                    clientSessionCode.setAction(ClientSessionModel.Action.AUTHENTICATE.name());
                     clientSession.setRedirectUri(redirectUri);
                     clientSession.setNote(OIDCLoginProtocol.STATE_PARAM, UUID.randomUUID().toString());
 
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java b/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java
index 55d97b0..5714047 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java
@@ -788,7 +788,7 @@ public class UsersResource {
 
         ClientSessionModel clientSession = createClientSession(user, redirectUri, clientId);
         ClientSessionCode accessCode = new ClientSessionCode(realm, clientSession);
-        accessCode.setAction(ClientSessionModel.Action.RECOVER_PASSWORD);
+        accessCode.setAction(ClientSessionModel.Action.RECOVER_PASSWORD.name());
 
         try {
             UriBuilder builder = Urls.loginPasswordResetBuilder(uriInfo.getBaseUri());
@@ -838,7 +838,7 @@ public class UsersResource {
         ClientSessionModel clientSession = createClientSession(user, redirectUri, clientId);
         ClientSessionCode accessCode = new ClientSessionCode(realm, clientSession);
 
-        accessCode.setAction(ClientSessionModel.Action.VERIFY_EMAIL);
+        accessCode.setAction(ClientSessionModel.Action.VERIFY_EMAIL.name());
 
         try {
             UriBuilder builder = Urls.loginActionEmailVerificationBuilder(uriInfo.getBaseUri());
diff --git a/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java b/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java
index e1d6bfa..5b27759 100755
--- a/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java
+++ b/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java
@@ -373,7 +373,7 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
     private ClientSessionCode parseClientSessionCode(String code) {
         ClientSessionCode clientCode = ClientSessionCode.parse(code, this.session, this.realmModel);
 
-        if (clientCode != null && clientCode.isValid(AUTHENTICATE)) {
+        if (clientCode != null && clientCode.isValid(AUTHENTICATE.name())) {
             ClientSessionModel clientSession = clientCode.getClientSession();
 
             if (clientSession != null) {
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 18c1d10..a81327e 100755
--- a/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java
+++ b/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java
@@ -166,7 +166,7 @@ public class LoginActionsService {
         ClientSessionCode clientCode;
         Response response;
 
-        boolean check(String code, ClientSessionModel.Action requiredAction) {
+        boolean check(String code, String requiredAction) {
             if (!check(code)) {
                 return false;
             } else if (!clientCode.isValid(requiredAction)) {
@@ -178,7 +178,7 @@ public class LoginActionsService {
             }
         }
 
-        boolean check(String code, ClientSessionModel.Action requiredAction, ClientSessionModel.Action alternativeRequiredAction) {
+        boolean check(String code, String requiredAction, String alternativeRequiredAction) {
             if (!check(code)) {
                 return false;
             } else if (!(clientCode.isValid(requiredAction) || clientCode.isValid(alternativeRequiredAction))) {
@@ -231,9 +231,9 @@ public class LoginActionsService {
         ClientSessionCode clientSessionCode = checks.clientCode;
         ClientSessionModel clientSession = clientSessionCode.getClientSession();
 
-        if (clientSession.getAction().equals(ClientSessionModel.Action.RECOVER_PASSWORD)) {
+        if (clientSession.getAction().equals(ClientSessionModel.Action.RECOVER_PASSWORD.name())) {
             TokenManager.dettachClientSession(session.sessions(), realm, clientSession);
-            clientSession.setAction(ClientSessionModel.Action.AUTHENTICATE);
+            clientSession.setAction(ClientSessionModel.Action.AUTHENTICATE.name());
         }
 
         return session.getProvider(LoginFormsProvider.class)
@@ -281,7 +281,8 @@ public class LoginActionsService {
     @Path("auth-form")
     @POST
     @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
-    public Response authForm(@QueryParam("code") String code) {
+    public Response authForm(@QueryParam("code") String code,
+                             @QueryParam("action") String action) {
         event.event(EventType.LOGIN);
         if (!checkSsl()) {
             event.error(Errors.SSL_REQUIRED);
@@ -301,8 +302,8 @@ public class LoginActionsService {
         ClientSessionModel clientSession = clientCode.getClientSession();
         event.detail(Details.CODE_ID, clientSession.getId());
 
-        if (!clientCode.isValid(ClientSessionModel.Action.AUTHENTICATE) || clientSession.getUserSession() != null) {
-            clientCode.setAction(ClientSessionModel.Action.AUTHENTICATE);
+        if (!clientCode.isValid(ClientSessionModel.Action.AUTHENTICATE.name()) || clientSession.getUserSession() != null) {
+            clientCode.setAction(ClientSessionModel.Action.AUTHENTICATE.name());
             event.client(clientSession.getClient()).error(Errors.EXPIRED_CODE);
             return session.getProvider(LoginFormsProvider.class)
                     .setError(Messages.EXPIRED_CODE)
@@ -338,39 +339,17 @@ public class LoginActionsService {
                 .setRealm(realm)
                 .setSession(session)
                 .setUriInfo(uriInfo)
+                .setAction(action)
                 .setRequest(request);
 
         try {
             return processor.authenticate();
-        } catch (AuthenticationProcessor.AuthException e) {
-            return handleError(e, code);
         } catch (Exception e) {
-            event.error(Errors.INVALID_USER_CREDENTIALS);
-            logger.error("failed authentication", e);
-            return ErrorPage.error(session, Messages.UNEXPECTED_ERROR_HANDLING_RESPONSE);
-
+            return processor.handleBrowserException(e);
         }
 
     }
 
-    protected Response handleError(AuthenticationProcessor.AuthException e, String code) {
-        logger.error("failed authentication: " + e.getError().toString(), e);
-        if (e.getError() == AuthenticationProcessor.Error.INVALID_USER) {
-            event.error(Errors.USER_NOT_FOUND);
-            return ErrorPage.error(session, Messages.INVALID_USER);
-        } else if (e.getError() == AuthenticationProcessor.Error.USER_DISABLED) {
-            event.error(Errors.USER_DISABLED);
-            return ErrorPage.error(session, Messages.ACCOUNT_DISABLED);
-        } else if (e.getError() == AuthenticationProcessor.Error.USER_TEMPORARILY_DISABLED) {
-            event.error(Errors.USER_TEMPORARILY_DISABLED);
-            return ErrorPage.error(session, Messages.ACCOUNT_TEMPORARILY_DISABLED);
-
-        } else {
-            event.error(Errors.INVALID_USER_CREDENTIALS);
-            return ErrorPage.error(session, Messages.INVALID_USER);
-        }
-    }
-
     /**
      * URL called after login page.  YOU SHOULD NEVER INVOKE THIS DIRECTLY!
      *
@@ -402,8 +381,8 @@ public class LoginActionsService {
         ClientSessionModel clientSession = clientCode.getClientSession();
         event.detail(Details.CODE_ID, clientSession.getId());
 
-        if (!clientCode.isValid(ClientSessionModel.Action.AUTHENTICATE) || clientSession.getUserSession() != null) {
-            clientCode.setAction(ClientSessionModel.Action.AUTHENTICATE);
+        if (!clientCode.isValid(ClientSessionModel.Action.AUTHENTICATE.name()) || clientSession.getUserSession() != null) {
+            clientCode.setAction(ClientSessionModel.Action.AUTHENTICATE.name());
             event.client(clientSession.getClient()).error(Errors.EXPIRED_CODE);
             return session.getProvider(LoginFormsProvider.class)
                     .setError(Messages.EXPIRED_CODE)
@@ -538,7 +517,7 @@ public class LoginActionsService {
             event.error(Errors.INVALID_CODE);
             return ErrorPage.error(session, Messages.INVALID_CODE);
         }
-        if (!clientCode.isValid(ClientSessionModel.Action.AUTHENTICATE)) {
+        if (!clientCode.isValid(ClientSessionModel.Action.AUTHENTICATE.name())) {
             event.error(Errors.INVALID_CODE);
             return ErrorPage.error(session, Messages.INVALID_CODE);
         }
@@ -676,7 +655,7 @@ public class LoginActionsService {
         String code = formData.getFirst("code");
 
         ClientSessionCode accessCode = ClientSessionCode.parse(code, session, realm);
-        if (accessCode == null || !accessCode.isValid(ClientSessionModel.Action.OAUTH_GRANT)) {
+        if (accessCode == null || !accessCode.isValid(ClientSessionModel.Action.OAUTH_GRANT.name())) {
             event.error(Errors.INVALID_CODE);
             return ErrorPage.error(session, Messages.INVALID_ACCESS_CODE);
         }
@@ -742,7 +721,7 @@ public class LoginActionsService {
                                   final MultivaluedMap<String, String> formData) {
         event.event(EventType.UPDATE_PROFILE);
         Checks checks = new Checks();
-        if (!checks.check(code, ClientSessionModel.Action.UPDATE_PROFILE)) {
+        if (!checks.check(code, ClientSessionModel.Action.UPDATE_PROFILE.name())) {
             return checks.response;
         }
         ClientSessionCode accessCode = checks.clientCode;
@@ -804,7 +783,7 @@ public class LoginActionsService {
                                final MultivaluedMap<String, String> formData) {
         event.event(EventType.UPDATE_TOTP);
         Checks checks = new Checks();
-        if (!checks.check(code, ClientSessionModel.Action.CONFIGURE_TOTP)) {
+        if (!checks.check(code, ClientSessionModel.Action.CONFIGURE_TOTP.name())) {
             return checks.response;
         }
         ClientSessionCode accessCode = checks.clientCode;
@@ -849,7 +828,7 @@ public class LoginActionsService {
                                    final MultivaluedMap<String, String> formData) {
         event.event(EventType.UPDATE_PASSWORD);
         Checks checks = new Checks();
-        if (!checks.check(code, ClientSessionModel.Action.UPDATE_PASSWORD, ClientSessionModel.Action.RECOVER_PASSWORD)) {
+        if (!checks.check(code, ClientSessionModel.Action.UPDATE_PASSWORD.name(), ClientSessionModel.Action.RECOVER_PASSWORD.name())) {
             return checks.response;
         }
         ClientSessionCode accessCode = checks.clientCode;
@@ -890,7 +869,7 @@ public class LoginActionsService {
 
         event.event(EventType.UPDATE_PASSWORD).success();
 
-        if (clientSession.getAction().equals(ClientSessionModel.Action.RECOVER_PASSWORD)) {
+        if (clientSession.getAction().equals(ClientSessionModel.Action.RECOVER_PASSWORD.name())) {
             String actionCookieValue = getActionCookie();
             if (actionCookieValue == null || !actionCookieValue.equals(userSession.getId())) {
                 return session.getProvider(LoginFormsProvider.class)
@@ -911,7 +890,7 @@ public class LoginActionsService {
         event.event(EventType.VERIFY_EMAIL);
         if (key != null) {
             Checks checks = new Checks();
-            if (!checks.check(key, ClientSessionModel.Action.VERIFY_EMAIL)) {
+            if (!checks.check(key, ClientSessionModel.Action.VERIFY_EMAIL.name())) {
                 return checks.response;
             }
             ClientSessionCode accessCode = checks.clientCode;
@@ -937,7 +916,7 @@ public class LoginActionsService {
             return redirectOauth(user, accessCode, clientSession, userSession);
         } else {
             Checks checks = new Checks();
-            if (!checks.check(code, ClientSessionModel.Action.VERIFY_EMAIL)) {
+            if (!checks.check(code, ClientSessionModel.Action.VERIFY_EMAIL.name())) {
                 return checks.response;
             }
             ClientSessionCode accessCode = checks.clientCode;
@@ -960,7 +939,7 @@ public class LoginActionsService {
         event.event(EventType.RESET_PASSWORD);
         if (key != null) {
             Checks checks = new Checks();
-            if (!checks.check(key, ClientSessionModel.Action.RECOVER_PASSWORD)) {
+            if (!checks.check(key, ClientSessionModel.Action.RECOVER_PASSWORD.name())) {
                 return checks.response;
             }
             ClientSessionCode accessCode = checks.clientCode;
@@ -1038,7 +1017,7 @@ public class LoginActionsService {
             event.session(userSession);
             TokenManager.attachClientSession(userSession, clientSession);
 
-            accessCode.setAction(ClientSessionModel.Action.RECOVER_PASSWORD);
+            accessCode.setAction(ClientSessionModel.Action.RECOVER_PASSWORD.name());
 
             try {
                 UriBuilder builder = Urls.loginPasswordResetBuilder(uriInfo.getBaseUri());
diff --git a/services/src/main/resources/META-INF/services/org.keycloak.authentication.AuthenticatorFactory b/services/src/main/resources/META-INF/services/org.keycloak.authentication.AuthenticatorFactory
index 8fbf02e..cb6bdc7 100755
--- a/services/src/main/resources/META-INF/services/org.keycloak.authentication.AuthenticatorFactory
+++ b/services/src/main/resources/META-INF/services/org.keycloak.authentication.AuthenticatorFactory
@@ -3,4 +3,5 @@ org.keycloak.authentication.authenticators.LoginFormOTPAuthenticatorFactory
 org.keycloak.authentication.authenticators.LoginFormPasswordAuthenticatorFactory
 org.keycloak.authentication.authenticators.LoginFormUsernameAuthenticatorFactory
 org.keycloak.authentication.authenticators.OTPFormAuthenticatorFactory
-org.keycloak.authentication.authenticators.SpnegoAuthenticatorFactory
\ No newline at end of file
+org.keycloak.authentication.authenticators.SpnegoAuthenticatorFactory
+org.keycloak.authentication.authenticators.SetRequiredActionAuthenticatorFactory
\ No newline at end of file
diff --git a/social/twitter/src/main/java/org/keycloak/social/twitter/TwitterIdentityProvider.java b/social/twitter/src/main/java/org/keycloak/social/twitter/TwitterIdentityProvider.java
index 64eeb62..d662141 100755
--- a/social/twitter/src/main/java/org/keycloak/social/twitter/TwitterIdentityProvider.java
+++ b/social/twitter/src/main/java/org/keycloak/social/twitter/TwitterIdentityProvider.java
@@ -166,7 +166,7 @@ public class TwitterIdentityProvider extends AbstractIdentityProvider<OAuth2Iden
         private ClientSessionCode parseClientSessionCode(String code) {
             ClientSessionCode clientCode = ClientSessionCode.parse(code, this.session, this.realm);
 
-            if (clientCode != null && clientCode.isValid(AUTHENTICATE)) {
+            if (clientCode != null && clientCode.isValid(AUTHENTICATE.name())) {
                 ClientSessionModel clientSession = clientCode.getClientSession();
 
                 if (clientSession != null) {
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/LoginTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/LoginTest.java
index c373780..d6f041f 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/LoginTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/LoginTest.java
@@ -1,437 +1,437 @@
-/*
- * JBoss, Home of Professional Open Source.
- * Copyright 2012, Red Hat, Inc., and individual contributors
- * as indicated by the @author tags. See the copyright.txt file in the
- * distribution for a full listing of individual contributors.
- *
- * This is free software; you can redistribute it and/or modify it
- * under the terms of the GNU Lesser General Public License as
- * published by the Free Software Foundation; either version 2.1 of
- * the License, or (at your option) any later version.
- *
- * This software is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this software; if not, write to the Free
- * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
- * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
- */
-package org.keycloak.testsuite.forms;
-
-import org.junit.Assert;
-import org.junit.ClassRule;
-import org.junit.Rule;
-import org.junit.Test;
-import org.keycloak.OAuth2Constants;
-import org.keycloak.events.Details;
-import org.keycloak.events.Event;
-import org.keycloak.events.EventType;
-import org.keycloak.models.BrowserSecurityHeaders;
-import org.keycloak.models.PasswordPolicy;
-import org.keycloak.models.RealmModel;
-import org.keycloak.models.UserCredentialModel;
-import org.keycloak.models.UserModel;
-import org.keycloak.representations.idm.CredentialRepresentation;
-import org.keycloak.services.managers.RealmManager;
-import org.keycloak.testsuite.AssertEvents;
-import org.keycloak.testsuite.OAuthClient;
-import org.keycloak.testsuite.pages.AppPage;
-import org.keycloak.testsuite.pages.AppPage.RequestType;
-import org.keycloak.testsuite.pages.LoginPage;
-import org.keycloak.testsuite.pages.LoginPasswordUpdatePage;
-import org.keycloak.testsuite.rule.KeycloakRule;
-import org.keycloak.testsuite.rule.WebResource;
-import org.keycloak.testsuite.rule.WebRule;
-import org.keycloak.util.Time;
-import org.openqa.selenium.WebDriver;
-
-import javax.ws.rs.client.Client;
-import javax.ws.rs.client.ClientBuilder;
-import javax.ws.rs.core.Response;
-
-import java.util.Map;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
-/**
- * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
- */
-public class LoginTest {
-
-    @ClassRule
-    public static KeycloakRule keycloakRule = new KeycloakRule(new KeycloakRule.KeycloakSetup() {
-        @Override
-        public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
-            UserModel user = manager.getSession().users().addUser(appRealm, "login-test");
-            user.setEmail("login@test.com");
-            user.setEnabled(true);
-
-            userId = user.getId();
-
-            UserCredentialModel creds = new UserCredentialModel();
-            creds.setType(CredentialRepresentation.PASSWORD);
-            creds.setValue("password");
-
-            user.updateCredential(creds);
-        }
-    });
-
-    @Rule
-    public AssertEvents events = new AssertEvents(keycloakRule);
-
-    @Rule
-    public WebRule webRule = new WebRule(this);
-
-    @WebResource
-    protected OAuthClient oauth;
-
-    @WebResource
-    protected WebDriver driver;
-
-    @WebResource
-    protected AppPage appPage;
-
-    @WebResource
-    protected LoginPage loginPage;
-    
-    @WebResource
-    protected LoginPasswordUpdatePage updatePasswordPage;
-
-    private static String userId;
-
-    @Test
-    public void testBrowserSecurityHeaders() {
-        Client client = ClientBuilder.newClient();
-        Response response = client.target(oauth.getLoginFormUrl()).request().get();
-        Assert.assertEquals(200, response.getStatus());
-        for (Map.Entry<String, String> entry : BrowserSecurityHeaders.defaultHeaders.entrySet()) {
-            String headerName = BrowserSecurityHeaders.headerAttributeMap.get(entry.getKey());
-            String headerValue = response.getHeaderString(headerName);
-            Assert.assertNotNull(headerValue);
-            Assert.assertEquals(headerValue, entry.getValue());
-        }
-        response.close();
-    }
-
-    @Test
-    public void loginInvalidPassword() {
-        loginPage.open();
-        loginPage.login("login-test", "invalid");
-
-        loginPage.assertCurrent();
-
-        Assert.assertEquals("Invalid username or password.", loginPage.getError());
-
-        events.expectLogin().user(userId).session((String) null).error("invalid_user_credentials").detail(Details.USERNAME, "login-test").assertEvent();
-    }
-
-    @Test
-    public void loginInvalidPasswordDisabledUser() {
-        keycloakRule.configure(new KeycloakRule.KeycloakSetup() {
-            @Override
-            public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
-                session.users().getUserByUsername("login-test", appRealm).setEnabled(false);
-            }
-        });
-
-        try {
-            loginPage.open();
-            loginPage.login("login-test", "invalid");
-
-            loginPage.assertCurrent();
-
-            Assert.assertEquals("Account is disabled, contact admin.", loginPage.getError());
-
-            events.expectLogin().user(userId).session((String) null).error("user_disabled").detail(Details.USERNAME, "login-test").assertEvent();
-        } finally {
-            keycloakRule.configure(new KeycloakRule.KeycloakSetup() {
-                @Override
-                public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
-                    session.users().getUserByUsername("login-test", appRealm).setEnabled(true);
-                }
-            });
-        }
-    }
-
-    @Test
-    public void loginDisabledUser() {
-        keycloakRule.configure(new KeycloakRule.KeycloakSetup() {
-            @Override
-            public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
-                session.users().getUserByUsername("login-test", appRealm).setEnabled(false);
-            }
-        });
-
-        try {
-            loginPage.open();
-            loginPage.login("login-test", "password");
-
-            loginPage.assertCurrent();
-
-            Assert.assertEquals("Account is disabled, contact admin.", loginPage.getError());
-
-            events.expectLogin().user(userId).session((String) null).error("user_disabled").detail(Details.USERNAME, "login-test").assertEvent();
-        } finally {
-            keycloakRule.configure(new KeycloakRule.KeycloakSetup() {
-                @Override
-                public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
-                    session.users().getUserByUsername("login-test", appRealm).setEnabled(true);
-                }
-            });
-        }
-    }
-
-    @Test
-    public void loginInvalidUsername() {
-        loginPage.open();
-        loginPage.login("invalid", "password");
-
-        loginPage.assertCurrent();
-
-        Assert.assertEquals("Invalid username or password.", loginPage.getError());
-
-        events.expectLogin().user((String) null).session((String) null).error("user_not_found").detail(Details.USERNAME, "invalid").assertEvent();
-    }
-
-    @Test
-    public void loginSuccess() {
-        loginPage.open();
-        loginPage.login("login-test", "password");
-        
-        Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
-        Assert.assertNotNull(oauth.getCurrentQuery().get(OAuth2Constants.CODE));
-
-        events.expectLogin().user(userId).detail(Details.USERNAME, "login-test").assertEvent();
-    }
-
-    @Test
-    public void loginPromptNone() {
-        driver.navigate().to(oauth.getLoginFormUrl().toString() + "&prompt=none");
-
-        assertFalse(loginPage.isCurrent());
-        assertTrue(appPage.isCurrent());
-
-        loginPage.open();
-        loginPage.login("login-test", "password");
-        Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
-
-        events.expectLogin().user(userId).detail(Details.USERNAME, "login-test").assertEvent();
-
-        driver.navigate().to(oauth.getLoginFormUrl().toString() + "&prompt=none");
-        Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
-
-        events.expectLogin().user(userId).removeDetail(Details.USERNAME).assertEvent();
-    }
-    
-    @Test
-    public void loginWithForcePasswordChangePolicy() {
-        keycloakRule.update(new KeycloakRule.KeycloakSetup() {
-            @Override
-            public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
-                appRealm.setPasswordPolicy(new PasswordPolicy("forceExpiredPasswordChange(1)"));
-            }
-        });
-        
-        try {
-            // Setting offset to more than one day to force password update
-            // elapsedTime > timeToExpire
-            Time.setOffset(86405);
-            
-            loginPage.open();
-
-            loginPage.login("login-test", "password");
-            
-            updatePasswordPage.assertCurrent();
-            
-            updatePasswordPage.changePassword("updatedPassword", "updatedPassword");
-
-            events.expectRequiredAction(EventType.UPDATE_PASSWORD).user(userId).detail(Details.USERNAME, "login-test").assertEvent();
-            
-            assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
-
-            events.expectLogin().user(userId).detail(Details.USERNAME, "login-test").assertEvent();
-            
-        } finally {
-            keycloakRule.update(new KeycloakRule.KeycloakSetup() {
-                @Override
-                public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
-                    appRealm.setPasswordPolicy(new PasswordPolicy(null));
-                    
-                    UserModel user = manager.getSession().users().getUserByUsername("login-test", appRealm);
-                    UserCredentialModel cred = new UserCredentialModel();
-                    cred.setType(CredentialRepresentation.PASSWORD);
-                    cred.setValue("password");
-                    user.updateCredential(cred);
-                }
-            });
-            Time.setOffset(0);
-        }
-    }
-    
-    @Test
-    public void loginWithoutForcePasswordChangePolicy() {
-        keycloakRule.update(new KeycloakRule.KeycloakSetup() {
-            @Override
-            public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
-                appRealm.setPasswordPolicy(new PasswordPolicy("forceExpiredPasswordChange(1)"));
-            }
-        });
-        
-        try {
-            // Setting offset to less than one day to avoid forced password update
-            // elapsedTime < timeToExpire
-            Time.setOffset(86205);
-            
-            loginPage.open();
-
-            loginPage.login("login-test", "password");
-            
-            Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
-            Assert.assertNotNull(oauth.getCurrentQuery().get(OAuth2Constants.CODE));
-
-            events.expectLogin().user(userId).detail(Details.USERNAME, "login-test").assertEvent();
-            
-        } finally {
-            keycloakRule.update(new KeycloakRule.KeycloakSetup() {
-                @Override
-                public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
-                    appRealm.setPasswordPolicy(new PasswordPolicy(null));
-                }
-            });
-            Time.setOffset(0);
-        }
-    }
-    
-    @Test
-    public void loginNoTimeoutWithLongWait() {
-        try {
-            loginPage.open();
-
-            Time.setOffset(1700);
-
-            loginPage.login("login-test", "password");
-
-            events.expectLogin().user(userId).detail(Details.USERNAME, "login-test").assertEvent().getSessionId();
-        } finally {
-            Time.setOffset(0);
-        }
-    }
-
-    @Test
-    public void loginTimeout() {
-        try {
-            loginPage.open();
-
-            Time.setOffset(1850);
-
-            loginPage.login("login-test", "password");
-
-            events.expectLogin().clearDetails().detail(Details.CODE_ID, AssertEvents.isCodeId()).user((String) null).session((String) null).error("expired_code").assertEvent().getSessionId();
-        } finally {
-            Time.setOffset(0);
-        }
-    }
-
-    @Test
-    public void loginLoginHint() {
-        String loginFormUrl = oauth.getLoginFormUrl() + "&login_hint=login-test";
-        driver.navigate().to(loginFormUrl);
-
-        Assert.assertEquals("login-test", loginPage.getUsername());
-        loginPage.login("password");
-
-        Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
-        Assert.assertNotNull(oauth.getCurrentQuery().get(OAuth2Constants.CODE));
-
-        events.expectLogin().user(userId).detail(Details.USERNAME, "login-test").assertEvent();
-    }
-
-    @Test
-    public void loginWithEmailSuccess() {
-        loginPage.open();
-        loginPage.login("login@test.com", "password");
-
-        Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
-        Assert.assertNotNull(oauth.getCurrentQuery().get(OAuth2Constants.CODE));
-
-        events.expectLogin().user(userId).assertEvent();
-    }
-
-    @Test
-    public void loginWithRememberMe() {
-        keycloakRule.update(new KeycloakRule.KeycloakSetup() {
-            @Override
-            public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
-                appRealm.setRememberMe(true);
-            }
-        });
-
-        try {
-            loginPage.open();
-            assertFalse(loginPage.isRememberMeChecked());
-            loginPage.setRememberMe(true);
-            assertTrue(loginPage.isRememberMeChecked());
-            loginPage.login("login-test", "password");
-
-            Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
-            Assert.assertNotNull(oauth.getCurrentQuery().get(OAuth2Constants.CODE));
-            Event loginEvent = events.expectLogin().user(userId)
-                    .detail(Details.USERNAME, "login-test")
-                    .detail(Details.REMEMBER_ME, "true")
-                    .assertEvent();
-            String sessionId = loginEvent.getSessionId();
-
-            // Expire session
-            keycloakRule.removeUserSession(sessionId);
-
-            // Assert rememberMe checked and username/email prefilled
-            loginPage.open();
-            assertTrue(loginPage.isRememberMeChecked());
-            Assert.assertEquals("login-test", loginPage.getUsername());
-
-            loginPage.setRememberMe(false);
-        } finally {
-            keycloakRule.update(new KeycloakRule.KeycloakSetup() {
-                @Override
-                public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
-                    appRealm.setRememberMe(false);
-                }
-            });
-        }
-    }
-
-    @Test
-    public void loginCancel() {
-        loginPage.open();
-        loginPage.cancel();
-
-        Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
-        Assert.assertEquals("access_denied", oauth.getCurrentQuery().get(OAuth2Constants.ERROR));
-
-        events.expectLogin().error("rejected_by_user").user((String) null).session((String) null).removeDetail(Details.USERNAME).assertEvent();
-    }
-
-    // KEYCLOAK-1037
-    @Test
-    public void loginExpiredCode() {
-        try {
-            loginPage.open();
-            Time.setOffset(5000);
-            loginPage.login("login@test.com", "password");
-
-            loginPage.assertCurrent();
-            Assert.assertEquals("Login timeout. Please login again.", loginPage.getError());
-
-            events.expectLogin().user((String) null).session((String) null).error("expired_code").clearDetails().detail(Details.CODE_ID, AssertEvents.isCodeId()).assertEvent();
-
-        } finally {
-            Time.setOffset(0);
-        }
-    }
-
-}
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2012, Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.keycloak.testsuite.forms;
+
+import org.junit.Assert;
+import org.junit.ClassRule;
+import org.junit.Rule;
+import org.junit.Test;
+import org.keycloak.OAuth2Constants;
+import org.keycloak.events.Details;
+import org.keycloak.events.Event;
+import org.keycloak.events.EventType;
+import org.keycloak.models.BrowserSecurityHeaders;
+import org.keycloak.models.PasswordPolicy;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserCredentialModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.representations.idm.CredentialRepresentation;
+import org.keycloak.services.managers.RealmManager;
+import org.keycloak.testsuite.AssertEvents;
+import org.keycloak.testsuite.OAuthClient;
+import org.keycloak.testsuite.pages.AppPage;
+import org.keycloak.testsuite.pages.AppPage.RequestType;
+import org.keycloak.testsuite.pages.LoginPage;
+import org.keycloak.testsuite.pages.LoginPasswordUpdatePage;
+import org.keycloak.testsuite.rule.KeycloakRule;
+import org.keycloak.testsuite.rule.WebResource;
+import org.keycloak.testsuite.rule.WebRule;
+import org.keycloak.util.Time;
+import org.openqa.selenium.WebDriver;
+
+import javax.ws.rs.client.Client;
+import javax.ws.rs.client.ClientBuilder;
+import javax.ws.rs.core.Response;
+
+import java.util.Map;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class LoginTest {
+
+    @ClassRule
+    public static KeycloakRule keycloakRule = new KeycloakRule(new KeycloakRule.KeycloakSetup() {
+        @Override
+        public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
+            UserModel user = manager.getSession().users().addUser(appRealm, "login-test");
+            user.setEmail("login@test.com");
+            user.setEnabled(true);
+
+            userId = user.getId();
+
+            UserCredentialModel creds = new UserCredentialModel();
+            creds.setType(CredentialRepresentation.PASSWORD);
+            creds.setValue("password");
+
+            user.updateCredential(creds);
+        }
+    });
+
+    @Rule
+    public AssertEvents events = new AssertEvents(keycloakRule);
+
+    @Rule
+    public WebRule webRule = new WebRule(this);
+
+    @WebResource
+    protected OAuthClient oauth;
+
+    @WebResource
+    protected WebDriver driver;
+
+    @WebResource
+    protected AppPage appPage;
+
+    @WebResource
+    protected LoginPage loginPage;
+    
+    @WebResource
+    protected LoginPasswordUpdatePage updatePasswordPage;
+
+    private static String userId;
+
+    @Test
+    public void testBrowserSecurityHeaders() {
+        Client client = ClientBuilder.newClient();
+        Response response = client.target(oauth.getLoginFormUrl()).request().get();
+        Assert.assertEquals(200, response.getStatus());
+        for (Map.Entry<String, String> entry : BrowserSecurityHeaders.defaultHeaders.entrySet()) {
+            String headerName = BrowserSecurityHeaders.headerAttributeMap.get(entry.getKey());
+            String headerValue = response.getHeaderString(headerName);
+            Assert.assertNotNull(headerValue);
+            Assert.assertEquals(headerValue, entry.getValue());
+        }
+        response.close();
+    }
+
+    @Test
+    public void loginInvalidPassword() {
+        loginPage.open();
+        loginPage.login("login-test", "invalid");
+
+        loginPage.assertCurrent();
+
+        Assert.assertEquals("Invalid username or password.", loginPage.getError());
+
+        events.expectLogin().user(userId).session((String) null).error("invalid_user_credentials").detail(Details.USERNAME, "login-test").assertEvent();
+    }
+
+    @Test
+    public void loginInvalidPasswordDisabledUser() {
+        keycloakRule.configure(new KeycloakRule.KeycloakSetup() {
+            @Override
+            public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
+                session.users().getUserByUsername("login-test", appRealm).setEnabled(false);
+            }
+        });
+
+        try {
+            loginPage.open();
+            loginPage.login("login-test", "invalid");
+
+            loginPage.assertCurrent();
+
+            Assert.assertEquals("Account is disabled, contact admin.", loginPage.getError());
+
+            events.expectLogin().user(userId).session((String) null).error("user_disabled").detail(Details.USERNAME, "login-test").assertEvent();
+        } finally {
+            keycloakRule.configure(new KeycloakRule.KeycloakSetup() {
+                @Override
+                public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
+                    session.users().getUserByUsername("login-test", appRealm).setEnabled(true);
+                }
+            });
+        }
+    }
+
+    @Test
+    public void loginDisabledUser() {
+        keycloakRule.configure(new KeycloakRule.KeycloakSetup() {
+            @Override
+            public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
+                session.users().getUserByUsername("login-test", appRealm).setEnabled(false);
+            }
+        });
+
+        try {
+            loginPage.open();
+            loginPage.login("login-test", "password");
+
+            loginPage.assertCurrent();
+
+            Assert.assertEquals("Account is disabled, contact admin.", loginPage.getError());
+
+            events.expectLogin().user(userId).session((String) null).error("user_disabled").detail(Details.USERNAME, "login-test").assertEvent();
+        } finally {
+            keycloakRule.configure(new KeycloakRule.KeycloakSetup() {
+                @Override
+                public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
+                    session.users().getUserByUsername("login-test", appRealm).setEnabled(true);
+                }
+            });
+        }
+    }
+
+    @Test
+    public void loginInvalidUsername() {
+        loginPage.open();
+        loginPage.login("invalid", "password");
+
+        loginPage.assertCurrent();
+
+        Assert.assertEquals("Invalid username or password.", loginPage.getError());
+
+        events.expectLogin().user((String) null).session((String) null).error("user_not_found").detail(Details.USERNAME, "invalid").assertEvent();
+    }
+
+    @Test
+    public void loginSuccess() {
+        loginPage.open();
+        loginPage.login("login-test", "password");
+        
+        Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
+        Assert.assertNotNull(oauth.getCurrentQuery().get(OAuth2Constants.CODE));
+
+        events.expectLogin().user(userId).detail(Details.USERNAME, "login-test").assertEvent();
+    }
+
+    @Test
+    public void loginPromptNone() {
+        driver.navigate().to(oauth.getLoginFormUrl().toString() + "&prompt=none");
+
+        assertFalse(loginPage.isCurrent());
+        assertTrue(appPage.isCurrent());
+
+        loginPage.open();
+        loginPage.login("login-test", "password");
+        Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
+
+        events.expectLogin().user(userId).detail(Details.USERNAME, "login-test").assertEvent();
+
+        driver.navigate().to(oauth.getLoginFormUrl().toString() + "&prompt=none");
+        Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
+
+        events.expectLogin().user(userId).removeDetail(Details.USERNAME).assertEvent();
+    }
+    
+    @Test
+    public void loginWithForcePasswordChangePolicy() {
+        keycloakRule.update(new KeycloakRule.KeycloakSetup() {
+            @Override
+            public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
+                appRealm.setPasswordPolicy(new PasswordPolicy("forceExpiredPasswordChange(1)"));
+            }
+        });
+        
+        try {
+            // Setting offset to more than one day to force password update
+            // elapsedTime > timeToExpire
+            Time.setOffset(86405);
+            
+            loginPage.open();
+
+            loginPage.login("login-test", "password");
+            
+            updatePasswordPage.assertCurrent();
+            
+            updatePasswordPage.changePassword("updatedPassword", "updatedPassword");
+
+            events.expectRequiredAction(EventType.UPDATE_PASSWORD).user(userId).detail(Details.USERNAME, "login-test").assertEvent();
+            
+            assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
+
+            events.expectLogin().user(userId).detail(Details.USERNAME, "login-test").assertEvent();
+            
+        } finally {
+            keycloakRule.update(new KeycloakRule.KeycloakSetup() {
+                @Override
+                public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
+                    appRealm.setPasswordPolicy(new PasswordPolicy(null));
+                    
+                    UserModel user = manager.getSession().users().getUserByUsername("login-test", appRealm);
+                    UserCredentialModel cred = new UserCredentialModel();
+                    cred.setType(CredentialRepresentation.PASSWORD);
+                    cred.setValue("password");
+                    user.updateCredential(cred);
+                }
+            });
+            Time.setOffset(0);
+        }
+    }
+    
+    @Test
+    public void loginWithoutForcePasswordChangePolicy() {
+        keycloakRule.update(new KeycloakRule.KeycloakSetup() {
+            @Override
+            public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
+                appRealm.setPasswordPolicy(new PasswordPolicy("forceExpiredPasswordChange(1)"));
+            }
+        });
+        
+        try {
+            // Setting offset to less than one day to avoid forced password update
+            // elapsedTime < timeToExpire
+            Time.setOffset(86205);
+            
+            loginPage.open();
+
+            loginPage.login("login-test", "password");
+            
+            Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
+            Assert.assertNotNull(oauth.getCurrentQuery().get(OAuth2Constants.CODE));
+
+            events.expectLogin().user(userId).detail(Details.USERNAME, "login-test").assertEvent();
+            
+        } finally {
+            keycloakRule.update(new KeycloakRule.KeycloakSetup() {
+                @Override
+                public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
+                    appRealm.setPasswordPolicy(new PasswordPolicy(null));
+                }
+            });
+            Time.setOffset(0);
+        }
+    }
+    
+    @Test
+    public void loginNoTimeoutWithLongWait() {
+        try {
+            loginPage.open();
+
+            Time.setOffset(1700);
+
+            loginPage.login("login-test", "password");
+
+            events.expectLogin().user(userId).detail(Details.USERNAME, "login-test").assertEvent().getSessionId();
+        } finally {
+            Time.setOffset(0);
+        }
+    }
+
+    @Test
+    public void loginTimeout() {
+        try {
+            loginPage.open();
+
+            Time.setOffset(1850);
+
+            loginPage.login("login-test", "password");
+
+            events.expectLogin().clearDetails().detail(Details.CODE_ID, AssertEvents.isCodeId()).user((String) null).session((String) null).error("expired_code").assertEvent().getSessionId();
+        } finally {
+            Time.setOffset(0);
+        }
+    }
+
+    @Test
+    public void loginLoginHint() {
+        String loginFormUrl = oauth.getLoginFormUrl() + "&login_hint=login-test";
+        driver.navigate().to(loginFormUrl);
+
+        Assert.assertEquals("login-test", loginPage.getUsername());
+        loginPage.login("password");
+
+        Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
+        Assert.assertNotNull(oauth.getCurrentQuery().get(OAuth2Constants.CODE));
+
+        events.expectLogin().user(userId).detail(Details.USERNAME, "login-test").assertEvent();
+    }
+
+    @Test
+    public void loginWithEmailSuccess() {
+        loginPage.open();
+        loginPage.login("login@test.com", "password");
+
+        Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
+        Assert.assertNotNull(oauth.getCurrentQuery().get(OAuth2Constants.CODE));
+
+        events.expectLogin().user(userId).assertEvent();
+    }
+
+    @Test
+    public void loginWithRememberMe() {
+        keycloakRule.update(new KeycloakRule.KeycloakSetup() {
+            @Override
+            public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
+                appRealm.setRememberMe(true);
+            }
+        });
+
+        try {
+            loginPage.open();
+            assertFalse(loginPage.isRememberMeChecked());
+            loginPage.setRememberMe(true);
+            assertTrue(loginPage.isRememberMeChecked());
+            loginPage.login("login-test", "password");
+
+            Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
+            Assert.assertNotNull(oauth.getCurrentQuery().get(OAuth2Constants.CODE));
+            Event loginEvent = events.expectLogin().user(userId)
+                    .detail(Details.USERNAME, "login-test")
+                    .detail(Details.REMEMBER_ME, "true")
+                    .assertEvent();
+            String sessionId = loginEvent.getSessionId();
+
+            // Expire session
+            keycloakRule.removeUserSession(sessionId);
+
+            // Assert rememberMe checked and username/email prefilled
+            loginPage.open();
+            assertTrue(loginPage.isRememberMeChecked());
+            Assert.assertEquals("login-test", loginPage.getUsername());
+
+            loginPage.setRememberMe(false);
+        } finally {
+            keycloakRule.update(new KeycloakRule.KeycloakSetup() {
+                @Override
+                public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
+                    appRealm.setRememberMe(false);
+                }
+            });
+        }
+    }
+
+    @Test
+    public void loginCancel() {
+        loginPage.open();
+        loginPage.cancel();
+
+        Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
+        Assert.assertEquals("access_denied", oauth.getCurrentQuery().get(OAuth2Constants.ERROR));
+
+        events.expectLogin().error("rejected_by_user").user((String) null).session((String) null).removeDetail(Details.USERNAME).assertEvent();
+    }
+
+    // KEYCLOAK-1037
+    @Test
+    public void loginExpiredCode() {
+        try {
+            loginPage.open();
+            Time.setOffset(5000);
+            loginPage.login("login@test.com", "password");
+
+            loginPage.assertCurrent();
+            Assert.assertEquals("Login timeout. Please login again.", loginPage.getError());
+
+            events.expectLogin().user((String) null).session((String) null).error("expired_code").clearDetails().detail(Details.CODE_ID, AssertEvents.isCodeId()).assertEvent();
+
+        } finally {
+            Time.setOffset(0);
+        }
+    }
+
+}
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserSessionProviderTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserSessionProviderTest.java
index c5b236a..7ca33e2 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserSessionProviderTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserSessionProviderTest.java
@@ -117,14 +117,14 @@ public class UserSessionProviderTest {
         int time = clientSession.getTimestamp();
         assertEquals(null, clientSession.getAction());
 
-        clientSession.setAction(ClientSessionModel.Action.CODE_TO_TOKEN);
+        clientSession.setAction(ClientSessionModel.Action.CODE_TO_TOKEN.name());
         clientSession.setTimestamp(time + 10);
 
         kc.stopSession(session, true);
         session = kc.startSession();
 
         ClientSessionModel updated = session.sessions().getClientSession(realm, id);
-        assertEquals(ClientSessionModel.Action.CODE_TO_TOKEN, updated.getAction());
+        assertEquals(ClientSessionModel.Action.CODE_TO_TOKEN.name(), updated.getAction());
         assertEquals(time + 10, updated.getTimestamp());
     }