keycloak-aplcache

registration flow

6/29/2015 11:12:06 PM

Changes

Details

diff --git a/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/model/UrlBean.java b/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/model/UrlBean.java
index 25a28c9..011e7f3 100755
--- a/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/model/UrlBean.java
+++ b/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/model/UrlBean.java
@@ -56,6 +56,9 @@ public class UrlBean {
     }
 
     public String getRegistrationAction() {
+        if (this.actionuri != null) {
+            return this.actionuri.toString();
+        }
         return Urls.realmRegisterAction(baseURI, realm).toString();
     }
 
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 7512422..3f2a103 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
@@ -2,7 +2,6 @@ package org.keycloak.models.utils;
 
 import org.keycloak.models.AuthenticationExecutionModel;
 import org.keycloak.models.AuthenticationFlowModel;
-import org.keycloak.models.AuthenticatorConfigModel;
 import org.keycloak.models.RealmModel;
 
 /**
@@ -11,10 +10,80 @@ import org.keycloak.models.RealmModel;
  */
 public class DefaultAuthenticationFlows {
 
+    public static final String REGISTRATION_FLOW = "registration";
+    public static final String REGISTRATION_FORM_FLOW = "registration form";
     public static final String BROWSER_FLOW = "browser";
-    public static final String FORMS_FLOW = "forms";
+    public static final String LOGIN_FORMS_FLOW = "forms";
 
     public static void addFlows(RealmModel realm) {
+        browserFlow(realm);
+        registrationFlow(realm);
+
+    }
+
+    public static void registrationFlow(RealmModel realm) {
+        AuthenticationFlowModel registrationFlow = new AuthenticationFlowModel();
+        registrationFlow.setAlias(REGISTRATION_FLOW);
+        registrationFlow.setDescription("registration flow");
+        registrationFlow.setProviderId("basic-flow");
+        registrationFlow = realm.addAuthenticationFlow(registrationFlow);
+
+        AuthenticationFlowModel registrationFormFlow = new AuthenticationFlowModel();
+        registrationFormFlow.setAlias(REGISTRATION_FORM_FLOW);
+        registrationFormFlow.setDescription("registration form");
+        registrationFormFlow.setProviderId("form-flow");
+        registrationFormFlow = realm.addAuthenticationFlow(registrationFormFlow);
+
+        AuthenticationExecutionModel execution = new AuthenticationExecutionModel();
+        execution.setParentFlow(registrationFlow.getId());
+        execution.setRequirement(AuthenticationExecutionModel.Requirement.REQUIRED);
+        execution.setAuthenticator("registration-page-form");
+        execution.setPriority(10);
+        execution.setUserSetupAllowed(false);
+        execution.setAutheticatorFlow(true);
+        execution.setFlowId(registrationFormFlow.getId());
+        realm.addAuthenticatorExecution(execution);
+
+        execution = new AuthenticationExecutionModel();
+        execution.setParentFlow(registrationFormFlow.getId());
+        execution.setRequirement(AuthenticationExecutionModel.Requirement.REQUIRED);
+        execution.setAuthenticator("username-validation-action");
+        execution.setPriority(20);
+        execution.setUserSetupAllowed(false);
+        execution.setAutheticatorFlow(false);
+        realm.addAuthenticatorExecution(execution);
+
+        execution = new AuthenticationExecutionModel();
+        execution.setParentFlow(registrationFormFlow.getId());
+        execution.setRequirement(AuthenticationExecutionModel.Requirement.REQUIRED);
+        execution.setAuthenticator("profile-validation-action");
+        execution.setPriority(30);
+        execution.setUserSetupAllowed(false);
+        execution.setAutheticatorFlow(false);
+        realm.addAuthenticatorExecution(execution);
+
+        execution = new AuthenticationExecutionModel();
+        execution.setParentFlow(registrationFormFlow.getId());
+        execution.setRequirement(AuthenticationExecutionModel.Requirement.REQUIRED);
+        execution.setAuthenticator("password-validation-action");
+        execution.setPriority(40);
+        execution.setUserSetupAllowed(false);
+        execution.setAutheticatorFlow(false);
+        realm.addAuthenticatorExecution(execution);
+
+        execution = new AuthenticationExecutionModel();
+        execution.setParentFlow(registrationFormFlow.getId());
+        execution.setRequirement(AuthenticationExecutionModel.Requirement.REQUIRED);
+        execution.setAuthenticator("registration-user-creation");
+        execution.setPriority(50);
+        execution.setUserSetupAllowed(false);
+        execution.setAutheticatorFlow(false);
+        realm.addAuthenticatorExecution(execution);
+
+
+    }
+
+    public static void browserFlow(RealmModel realm) {
         AuthenticationFlowModel browser = new AuthenticationFlowModel();
         browser.setAlias(BROWSER_FLOW);
         browser.setDescription("browser based authentication");
@@ -39,7 +108,7 @@ public class DefaultAuthenticationFlows {
 
 
         AuthenticationFlowModel forms = new AuthenticationFlowModel();
-        forms.setAlias(FORMS_FLOW);
+        forms.setAlias(LOGIN_FORMS_FLOW);
         forms.setDescription("Username, password, otp and other auth forms.");
         forms.setProviderId("basic-flow");
         forms = realm.addAuthenticationFlow(forms);
@@ -72,8 +141,5 @@ public class DefaultAuthenticationFlows {
         execution.setUserSetupAllowed(true);
         execution.setAutheticatorFlow(false);
         realm.addAuthenticatorExecution(execution);
-
-        //
-
     }
 }
diff --git a/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java b/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java
index f5ff56e..161ffe0 100755
--- a/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java
+++ b/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java
@@ -26,6 +26,7 @@ import org.keycloak.util.Time;
 
 import javax.ws.rs.core.Response;
 import javax.ws.rs.core.UriInfo;
+import java.util.List;
 
 /**
  * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@@ -167,10 +168,25 @@ public class AuthenticationProcessor {
         Status status;
         Response challenge;
         Error error;
+        List<AuthenticationExecutionModel> currentExecutions;
 
-        private Result(AuthenticationExecutionModel execution, Authenticator authenticator) {
+        private Result(AuthenticationExecutionModel execution, Authenticator authenticator, List<AuthenticationExecutionModel> currentExecutions) {
             this.execution = execution;
             this.authenticator = authenticator;
+            this.currentExecutions = currentExecutions;
+        }
+
+        @Override
+        public AuthenticationExecutionModel.Requirement getCategoryRequirementFromCurrentFlow(String authenticatorCategory) {
+            List<AuthenticationExecutionModel> executions = realm.getAuthenticationExecutions(execution.getParentFlow());
+            for (AuthenticationExecutionModel exe : executions) {
+                AuthenticatorFactory factory = (AuthenticatorFactory) getSession().getKeycloakSessionFactory().getProviderFactory(Authenticator.class, exe.getAuthenticator());
+                if (factory != null && factory.getReferenceCategory().equals(authenticatorCategory)) {
+                    return exe.getRequirement();
+                }
+
+            }
+            return null;
         }
 
         @Override
@@ -434,8 +450,7 @@ public class AuthenticationProcessor {
             throw new AuthException(Error.INTERNAL_ERROR);
         }
         if (flow.getProviderId() == null || flow.getProviderId().equals("basic-flow")) {
-            DefaultAuthenticationFlow flowExecution = new DefaultAuthenticationFlow(this);
-            flowExecution.executions = realm.getAuthenticationExecutions(flow.getId()).iterator();
+            DefaultAuthenticationFlow flowExecution = new DefaultAuthenticationFlow(this, flow);
             return flowExecution;
 
         } else if (flow.getProviderId().equals("form-flow")) {
@@ -587,8 +602,8 @@ public class AuthenticationProcessor {
 
     }
 
-    public AuthenticatorContext createAuthenticatorContext(AuthenticationExecutionModel model, Authenticator authenticator) {
-        return new Result(model, authenticator);
+    public AuthenticatorContext createAuthenticatorContext(AuthenticationExecutionModel model, Authenticator authenticator, List<AuthenticationExecutionModel> executions) {
+        return new Result(model, authenticator, executions);
     }
 
 
diff --git a/services/src/main/java/org/keycloak/authentication/AuthenticatorContext.java b/services/src/main/java/org/keycloak/authentication/AuthenticatorContext.java
index 07a3fd1..c73e5ec 100755
--- a/services/src/main/java/org/keycloak/authentication/AuthenticatorContext.java
+++ b/services/src/main/java/org/keycloak/authentication/AuthenticatorContext.java
@@ -54,6 +54,8 @@ public interface AuthenticatorContext {
     HttpRequest getHttpRequest();
     BruteForceProtector getProtector();
 
+    AuthenticationExecutionModel.Requirement getCategoryRequirementFromCurrentFlow(String authenticatorCategory);
+
     void success();
     void failure(AuthenticationProcessor.Error error);
     void failure(AuthenticationProcessor.Error error, Response response);
diff --git a/services/src/main/java/org/keycloak/authentication/AuthenticatorFactory.java b/services/src/main/java/org/keycloak/authentication/AuthenticatorFactory.java
index 2e6178c..f928bea 100755
--- a/services/src/main/java/org/keycloak/authentication/AuthenticatorFactory.java
+++ b/services/src/main/java/org/keycloak/authentication/AuthenticatorFactory.java
@@ -9,24 +9,7 @@ import org.keycloak.provider.ProviderFactory;
 * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
 * @version $Revision: 1 $
 */
-public interface AuthenticatorFactory extends ProviderFactory<Authenticator>, ConfiguredProvider {
+public interface AuthenticatorFactory extends ProviderFactory<Authenticator>, ConfiguredProvider, ConfigurableAuthenticatorFactory {
     Authenticator create();
-    String getDisplayType();
-
-    /**
-     * General authenticator type, i.e. totp, password, cert.
-     *
-     * @return null if not a referencable type
-     */
-    String getReferenceType();
-
-    boolean isConfigurable();
-
-    /**
-     * What requirement settings are allowed.
-     *
-     * @return
-     */
-    AuthenticationExecutionModel.Requirement[] getRequirementChoices();
 
 }
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/CookieAuthenticatorFactory.java b/services/src/main/java/org/keycloak/authentication/authenticators/CookieAuthenticatorFactory.java
index d45d2ae..5889b8c 100755
--- a/services/src/main/java/org/keycloak/authentication/authenticators/CookieAuthenticatorFactory.java
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/CookieAuthenticatorFactory.java
@@ -49,7 +49,7 @@ public class CookieAuthenticatorFactory implements AuthenticatorFactory {
     }
 
     @Override
-    public String getReferenceType() {
+    public String getReferenceCategory() {
         return "cookie";
     }
 
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/OTPFormAuthenticatorFactory.java b/services/src/main/java/org/keycloak/authentication/authenticators/OTPFormAuthenticatorFactory.java
index 6e21a52..dd222fe 100755
--- a/services/src/main/java/org/keycloak/authentication/authenticators/OTPFormAuthenticatorFactory.java
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/OTPFormAuthenticatorFactory.java
@@ -51,7 +51,7 @@ public class OTPFormAuthenticatorFactory implements AuthenticatorFactory {
     }
 
     @Override
-    public String getReferenceType() {
+    public String getReferenceCategory() {
         return UserCredentialModel.TOTP;
     }
 
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/SpnegoAuthenticatorFactory.java b/services/src/main/java/org/keycloak/authentication/authenticators/SpnegoAuthenticatorFactory.java
index 8310d64..cd23b1e 100755
--- a/services/src/main/java/org/keycloak/authentication/authenticators/SpnegoAuthenticatorFactory.java
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/SpnegoAuthenticatorFactory.java
@@ -46,7 +46,7 @@ public class SpnegoAuthenticatorFactory implements AuthenticatorFactory {
     }
 
     @Override
-    public String getReferenceType() {
+    public String getReferenceCategory() {
         return UserCredentialModel.KERBEROS;
     }
 
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/UsernamePasswordFormFactory.java b/services/src/main/java/org/keycloak/authentication/authenticators/UsernamePasswordFormFactory.java
index 36b3d21..123c4b4 100755
--- a/services/src/main/java/org/keycloak/authentication/authenticators/UsernamePasswordFormFactory.java
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/UsernamePasswordFormFactory.java
@@ -51,7 +51,7 @@ public class UsernamePasswordFormFactory implements AuthenticatorFactory {
     }
 
     @Override
-    public String getReferenceType() {
+    public String getReferenceCategory() {
         return UserCredentialModel.PASSWORD;
     }
 
diff --git a/services/src/main/java/org/keycloak/authentication/ConfigurableAuthenticatorFactory.java b/services/src/main/java/org/keycloak/authentication/ConfigurableAuthenticatorFactory.java
new file mode 100755
index 0000000..cca47c6
--- /dev/null
+++ b/services/src/main/java/org/keycloak/authentication/ConfigurableAuthenticatorFactory.java
@@ -0,0 +1,27 @@
+package org.keycloak.authentication;
+
+import org.keycloak.models.AuthenticationExecutionModel;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public interface ConfigurableAuthenticatorFactory {
+    String getDisplayType();
+
+    /**
+     * General authenticator type, i.e. totp, password, cert.
+     *
+     * @return null if not a referencable category
+     */
+    String getReferenceCategory();
+
+    boolean isConfigurable();
+
+    /**
+     * What requirement settings are allowed.
+     *
+     * @return
+     */
+    AuthenticationExecutionModel.Requirement[] getRequirementChoices();
+}
diff --git a/services/src/main/java/org/keycloak/authentication/DefaultAuthenticationFlow.java b/services/src/main/java/org/keycloak/authentication/DefaultAuthenticationFlow.java
index 8aaaa64..54da8ad 100755
--- a/services/src/main/java/org/keycloak/authentication/DefaultAuthenticationFlow.java
+++ b/services/src/main/java/org/keycloak/authentication/DefaultAuthenticationFlow.java
@@ -1,11 +1,13 @@
 package org.keycloak.authentication;
 
 import org.keycloak.models.AuthenticationExecutionModel;
+import org.keycloak.models.AuthenticationFlowModel;
 import org.keycloak.models.ClientSessionModel;
 import org.keycloak.models.UserModel;
 
 import javax.ws.rs.core.Response;
 import java.util.Iterator;
+import java.util.List;
 
 /**
 * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@@ -15,11 +17,16 @@ public class DefaultAuthenticationFlow implements AuthenticationFlow {
     Response alternativeChallenge = null;
     AuthenticationExecutionModel challengedAlternativeExecution = null;
     boolean alternativeSuccessful = false;
-    Iterator<AuthenticationExecutionModel> executions;
+    List<AuthenticationExecutionModel> executions;
+    Iterator<AuthenticationExecutionModel> executionIterator;
     AuthenticationProcessor processor;
+    AuthenticationFlowModel flow;
 
-    public DefaultAuthenticationFlow(AuthenticationProcessor processor) {
+    public DefaultAuthenticationFlow(AuthenticationProcessor processor, AuthenticationFlowModel flow) {
         this.processor = processor;
+        this.flow = flow;
+        this.executions = processor.getRealm().getAuthenticationExecutions(flow.getId());
+        this.executionIterator = executions.iterator();
     }
 
     protected boolean isProcessed(AuthenticationExecutionModel model) {
@@ -34,25 +41,21 @@ public class DefaultAuthenticationFlow implements AuthenticationFlow {
 
     @Override
     public Response processAction(String actionExecution) {
-        while (executions.hasNext()) {
-            AuthenticationExecutionModel model = executions.next();
+        while (executionIterator.hasNext()) {
+            AuthenticationExecutionModel model = executionIterator.next();
             if (isProcessed(model)) {
                 AuthenticationProcessor.logger.debug("execution is processed");
                 if (!alternativeSuccessful && model.isAlternative() && processor.isSuccessful(model))
                     alternativeSuccessful = true;
                 continue;
             }
-            if (!model.getId().equals(actionExecution)) {
-                if (model.isAutheticatorFlow()) {
-                    AuthenticationFlow authenticationFlow = processor.createFlowExecution(model.getFlowId(), model);
-                    return authenticationFlow.processAction(actionExecution);
-                } else {
-                    throw new AuthenticationProcessor.AuthException("action is not current execution", AuthenticationProcessor.Error.INTERNAL_ERROR);
-                }
-            } else { // we found the action
+            if (model.isAutheticatorFlow()) {
+                AuthenticationFlow authenticationFlow = processor.createFlowExecution(model.getFlowId(), model);
+                return authenticationFlow.processAction(actionExecution);
+            } else if (model.getId().equals(actionExecution)) {
                 AuthenticatorFactory factory = (AuthenticatorFactory) processor.getSession().getKeycloakSessionFactory().getProviderFactory(Authenticator.class, model.getAuthenticator());
                 Authenticator authenticator = factory.create();
-                AuthenticatorContext result = processor.createAuthenticatorContext(model, authenticator);
+                AuthenticatorContext result = processor.createAuthenticatorContext(model, authenticator, executions);
                 authenticator.action(result);
                 Response response = processResult(result);
                 if (response == null) return processFlow();
@@ -64,8 +67,8 @@ public class DefaultAuthenticationFlow implements AuthenticationFlow {
 
     @Override
     public Response processFlow() {
-        while (executions.hasNext()) {
-            AuthenticationExecutionModel model = executions.next();
+        while (executionIterator.hasNext()) {
+            AuthenticationExecutionModel model = executionIterator.next();
             if (isProcessed(model)) {
                 AuthenticationProcessor.logger.debug("execution is processed");
                 if (!alternativeSuccessful && model.isAlternative() && processor.isSuccessful(model))
@@ -132,7 +135,7 @@ public class DefaultAuthenticationFlow implements AuthenticationFlow {
                     }
                 }
             }
-            AuthenticatorContext context = processor.createAuthenticatorContext(model, authenticator);
+            AuthenticatorContext context = processor.createAuthenticatorContext(model, authenticator, executions);
             authenticator.authenticate(context);
             Response response = processResult(context);
             if (response != null) return response;
diff --git a/services/src/main/java/org/keycloak/authentication/FormAction.java b/services/src/main/java/org/keycloak/authentication/FormAction.java
index 90bd084..b231829 100755
--- a/services/src/main/java/org/keycloak/authentication/FormAction.java
+++ b/services/src/main/java/org/keycloak/authentication/FormAction.java
@@ -10,7 +10,7 @@ import org.keycloak.provider.Provider;
  * @version $Revision: 1 $
  */
 public interface FormAction extends Provider {
-    void authenticate(FormContext context);
+    void authenticate(FormActionContext context);
 
     boolean requiresUser();
     boolean configuredFor(KeycloakSession session, RealmModel realm, UserModel user);
diff --git a/services/src/main/java/org/keycloak/authentication/FormActionFactory.java b/services/src/main/java/org/keycloak/authentication/FormActionFactory.java
index cbac58b..866cdfa 100755
--- a/services/src/main/java/org/keycloak/authentication/FormActionFactory.java
+++ b/services/src/main/java/org/keycloak/authentication/FormActionFactory.java
@@ -6,5 +6,5 @@ import org.keycloak.provider.ProviderFactory;
  * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
  * @version $Revision: 1 $
  */
-public interface FormActionFactory extends ProviderFactory<FormAction> {
+public interface FormActionFactory extends ProviderFactory<FormAction>, ConfigurableAuthenticatorFactory {
 }
diff --git a/services/src/main/java/org/keycloak/authentication/FormAuthenticationFlow.java b/services/src/main/java/org/keycloak/authentication/FormAuthenticationFlow.java
index b8bb691..1ef53eb 100755
--- a/services/src/main/java/org/keycloak/authentication/FormAuthenticationFlow.java
+++ b/services/src/main/java/org/keycloak/authentication/FormAuthenticationFlow.java
@@ -14,8 +14,10 @@ import org.keycloak.services.managers.BruteForceProtector;
 
 import javax.ws.rs.core.Response;
 import javax.ws.rs.core.UriInfo;
-import java.util.Iterator;
+import java.util.HashMap;
+import java.util.LinkedList;
 import java.util.List;
+import java.util.Map;
 
 /**
 * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@@ -23,26 +25,35 @@ import java.util.List;
 */
 public class FormAuthenticationFlow implements AuthenticationFlow {
     AuthenticationProcessor processor;
-    AuthenticationExecutionModel execution;
+    AuthenticationExecutionModel formExecution;
+    private final List<AuthenticationExecutionModel> formActionExecutions;
+    private final FormAuthenticator formAuthenticator;
 
 
     public FormAuthenticationFlow(AuthenticationProcessor processor, AuthenticationExecutionModel execution) {
         this.processor = processor;
-        this.execution = execution;
+        this.formExecution = execution;
+        formActionExecutions = processor.getRealm().getAuthenticationExecutions(execution.getFlowId());
+        formAuthenticator = processor.getSession().getProvider(FormAuthenticator.class, execution.getAuthenticator());
     }
 
-    private static class FormActionResult implements FormContext {
-        AuthenticatorContext delegate;
-        FormAuthenticator authenticator;
+    private class FormContext implements FormActionContext {
+        protected AuthenticatorContext delegate;
 
-        FormActionResult(AuthenticatorContext delegate, FormAuthenticator authenticator) {
+        private FormContext(AuthenticatorContext delegate) {
             this.delegate = delegate;
-            this.authenticator = authenticator;
         }
 
+
+
         @Override
         public FormAuthenticator getFormAuthenticator() {
-            return authenticator;
+            return formAuthenticator;
+        }
+
+        @Override
+        public AuthenticationExecutionModel getFormExecution() {
+            return formExecution;
         }
 
         @Override
@@ -136,6 +147,18 @@ public class FormAuthenticationFlow implements AuthenticationFlow {
         }
 
         @Override
+        public AuthenticationExecutionModel.Requirement getCategoryRequirementFromCurrentFlow(String authenticatorCategory) {
+            for (AuthenticationExecutionModel formActionExecution : formActionExecutions) {
+                FormActionFactory factory = (FormActionFactory) getSession().getKeycloakSessionFactory().getProviderFactory(FormAction.class, formActionExecution.getAuthenticator());
+                if (factory != null && authenticatorCategory.equals(factory.getReferenceCategory())) {
+                    return formActionExecution.getRequirement();
+                }
+
+            }
+            return null;
+        }
+
+        @Override
         public void success() {
             delegate.success();
         }
@@ -191,19 +214,19 @@ public class FormAuthenticationFlow implements AuthenticationFlow {
         }
     }
 
-
     @Override
     public Response processAction(String actionExecution) {
-        if (!actionExecution.equals(execution.getId())) {
+        if (!actionExecution.equals(formExecution.getId())) {
             throw new AuthenticationProcessor.AuthException("action is not current execution", AuthenticationProcessor.Error.INTERNAL_ERROR);
         }
-        FormAuthenticator authenticator = processor.getSession().getProvider(FormAuthenticator.class, execution.getAuthenticator());
-        for (AuthenticationExecutionModel formActionExecution : processor.getRealm().getAuthenticationExecutions(execution.getFlowId())) {
-            FormAction action = processor.getSession().getProvider(FormAction.class, execution.getAuthenticator());
+        Map<String, ClientSessionModel.ExecutionStatus> executionStatus = new HashMap<>();
+        List<FormAction> requiredActions = new LinkedList<>();
+        for (AuthenticationExecutionModel formActionExecution : formActionExecutions) {
+            FormAction action = processor.getSession().getProvider(FormAction.class, formActionExecution.getAuthenticator());
 
             UserModel authUser = processor.getClientSession().getAuthenticatedUser();
             if (action.requiresUser() && authUser == null) {
-                throw new AuthenticationProcessor.AuthException("form action: " + execution.getAuthenticator() + " requires user", AuthenticationProcessor.Error.UNKNOWN_USER);
+                throw new AuthenticationProcessor.AuthException("form action: " + formExecution.getAuthenticator() + " requires user", AuthenticationProcessor.Error.UNKNOWN_USER);
             }
             boolean configuredFor = false;
             if (action.requiresUser() && authUser != null) {
@@ -211,23 +234,33 @@ public class FormAuthenticationFlow implements AuthenticationFlow {
                 if (!configuredFor) {
                     if (formActionExecution.isRequired()) {
                         if (formActionExecution.isUserSetupAllowed()) {
-                            AuthenticationProcessor.logger.debugv("authenticator SETUP_REQUIRED: {0}",  execution.getAuthenticator());
-                            processor.getClientSession().setExecutionStatus(formActionExecution.getId(), ClientSessionModel.ExecutionStatus.SETUP_REQUIRED);
-                            action.setRequiredActions(processor.getSession(), processor.getRealm(), authUser);
+                            AuthenticationProcessor.logger.debugv("authenticator SETUP_REQUIRED: {0}", formExecution.getAuthenticator());
+                            executionStatus.put(formActionExecution.getId(), ClientSessionModel.ExecutionStatus.SETUP_REQUIRED);
+                            requiredActions.add(action);
                             continue;
                         } else {
                             throw new AuthenticationProcessor.AuthException(AuthenticationProcessor.Error.CREDENTIAL_SETUP_REQUIRED);
                         }
                     } else if (formActionExecution.isOptional()) {
-                        processor.getClientSession().setExecutionStatus(formActionExecution.getId(), ClientSessionModel.ExecutionStatus.SKIPPED);
+                        executionStatus.put(formActionExecution.getId(), ClientSessionModel.ExecutionStatus.SKIPPED);
                         continue;
                     }
                 }
             }
 
-            FormActionResult result = new FormActionResult(processor.createAuthenticatorContext(formActionExecution, null), authenticator);
+            AuthenticatorContext delegate = processor.createAuthenticatorContext(formActionExecution, null, formActionExecutions);
+            FormActionContext result = new FormContext(delegate);
             action.authenticate(result);
-            return processResult(result, formActionExecution);
+            Response challenge = processResult(executionStatus, result, formActionExecution);
+            if (challenge != null) return challenge;
+            executionStatus.put(formActionExecution.getId(), ClientSessionModel.ExecutionStatus.SUCCESS);
+        }
+        // set status and required actions only if form is fully successful
+        for (Map.Entry<String, ClientSessionModel.ExecutionStatus> entry : executionStatus.entrySet()) {
+            processor.getClientSession().setExecutionStatus(entry.getKey(), entry.getValue());
+        }
+        for (FormAction action : requiredActions) {
+            action.setRequiredActions(processor.getSession(), processor.getRealm(), processor.getClientSession().getAuthenticatedUser());
 
         }
         return null;
@@ -236,42 +269,48 @@ public class FormAuthenticationFlow implements AuthenticationFlow {
 
     @Override
     public Response processFlow() {
-        FormAuthenticator authenticator = processor.getSession().getProvider(FormAuthenticator.class, execution.getAuthenticator());
-        AuthenticatorContext context = processor.createAuthenticatorContext(execution, null);
-        authenticator.authenticate(context);
-        return processResult(context, execution);
+        AuthenticatorContext delegate = processor.createAuthenticatorContext(formExecution, null, formActionExecutions);
+        FormActionContext result = new FormContext(delegate);
+        formAuthenticator.authenticate(result);
+        Map<String, ClientSessionModel.ExecutionStatus> executionStatus = new HashMap<>();
+        Response response = processResult(executionStatus, result, formExecution);
+        for (Map.Entry<String, ClientSessionModel.ExecutionStatus> entry : executionStatus.entrySet()) {
+            processor.getClientSession().setExecutionStatus(entry.getKey(), entry.getValue());
+        }
+        return response;
     }
 
 
-    public Response processResult(AuthenticatorContext result, AuthenticationExecutionModel execution) {
+    public Response processResult(Map<String, ClientSessionModel.ExecutionStatus> executionStatus, AuthenticatorContext result, AuthenticationExecutionModel execution) {
         AuthenticationProcessor.Status status = result.getStatus();
         if (status == AuthenticationProcessor.Status.SUCCESS) {
+            executionStatus.put(execution.getId(), ClientSessionModel.ExecutionStatus.SUCCESS);
             return null;
         } else if (status == AuthenticationProcessor.Status.FAILED) {
             AuthenticationProcessor.logger.debugv("authenticator FAILED: {0}", execution.getAuthenticator());
             processor.logFailure();
-            processor.getClientSession().setExecutionStatus(execution.getId(), ClientSessionModel.ExecutionStatus.FAILED);
+            executionStatus.put(execution.getId(), ClientSessionModel.ExecutionStatus.FAILED);
             if (result.getChallenge() != null) {
-                return sendChallenge(result, execution);
+                return sendChallenge(result);
             }
             throw new AuthenticationProcessor.AuthException(result.getError());
         } else if (status == AuthenticationProcessor.Status.FORCE_CHALLENGE) {
-            processor.getClientSession().setExecutionStatus(execution.getId(), ClientSessionModel.ExecutionStatus.CHALLENGED);
-            return sendChallenge(result, execution);
+            executionStatus.put(execution.getId(), ClientSessionModel.ExecutionStatus.CHALLENGED);
+            return sendChallenge(result);
         } else if (status == AuthenticationProcessor.Status.CHALLENGE) {
             processor.getClientSession().setExecutionStatus(execution.getId(), ClientSessionModel.ExecutionStatus.CHALLENGED);
-            return sendChallenge(result, execution);
+            return sendChallenge(result);
         } else if (status == AuthenticationProcessor.Status.FAILURE_CHALLENGE) {
             AuthenticationProcessor.logger.debugv("authenticator FAILURE_CHALLENGE: {0}", execution.getAuthenticator());
             processor.logFailure();
-            processor.getClientSession().setExecutionStatus(execution.getId(), ClientSessionModel.ExecutionStatus.CHALLENGED);
-            return sendChallenge(result, execution);
+            executionStatus.put(execution.getId(), ClientSessionModel.ExecutionStatus.CHALLENGED);
+            return sendChallenge(result);
         } else if (status == AuthenticationProcessor.Status.ATTEMPTED) {
             AuthenticationProcessor.logger.debugv("authenticator ATTEMPTED: {0}", execution.getAuthenticator());
             if (execution.getRequirement() == AuthenticationExecutionModel.Requirement.REQUIRED) {
                 throw new AuthenticationProcessor.AuthException(AuthenticationProcessor.Error.INVALID_CREDENTIALS);
             }
-            processor.getClientSession().setExecutionStatus(execution.getId(), ClientSessionModel.ExecutionStatus.ATTEMPTED);
+            executionStatus.put(execution.getId(), ClientSessionModel.ExecutionStatus.ATTEMPTED);
             return null;
         } else {
             AuthenticationProcessor.logger.debugv("authenticator INTERNAL_ERROR: {0}", execution.getAuthenticator());
@@ -281,8 +320,8 @@ public class FormAuthenticationFlow implements AuthenticationFlow {
 
     }
 
-    public Response sendChallenge(AuthenticatorContext result, AuthenticationExecutionModel execution) {
-        processor.getClientSession().setNote(AuthenticationProcessor.CURRENT_AUTHENTICATION_EXECUTION, execution.getId());
+    public Response sendChallenge(AuthenticatorContext result) {
+        processor.getClientSession().setNote(AuthenticationProcessor.CURRENT_AUTHENTICATION_EXECUTION, formExecution.getId());
         return result.getChallenge();
     }
 
diff --git a/services/src/main/java/org/keycloak/authentication/FormAuthenticator.java b/services/src/main/java/org/keycloak/authentication/FormAuthenticator.java
index 4804e92..f33843e 100755
--- a/services/src/main/java/org/keycloak/authentication/FormAuthenticator.java
+++ b/services/src/main/java/org/keycloak/authentication/FormAuthenticator.java
@@ -1,8 +1,11 @@
 package org.keycloak.authentication;
 
+import org.keycloak.models.utils.FormMessage;
 import org.keycloak.provider.Provider;
 
+import javax.ws.rs.core.MultivaluedMap;
 import javax.ws.rs.core.Response;
+import java.util.List;
 
 /**
  * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@@ -10,5 +13,5 @@ import javax.ws.rs.core.Response;
  */
 public interface FormAuthenticator extends Provider {
     void authenticate(AuthenticatorContext context);
-    Response createChallenge(FormContext context, String... errorMessages);
+    Response createChallenge(FormActionContext context, MultivaluedMap<String, String> formData, List<FormMessage> errorMessages);
 }
diff --git a/services/src/main/java/org/keycloak/authentication/FormAuthenticatorFactory.java b/services/src/main/java/org/keycloak/authentication/FormAuthenticatorFactory.java
index 5b99f1a..a388f43 100755
--- a/services/src/main/java/org/keycloak/authentication/FormAuthenticatorFactory.java
+++ b/services/src/main/java/org/keycloak/authentication/FormAuthenticatorFactory.java
@@ -6,5 +6,5 @@ import org.keycloak.provider.ProviderFactory;
  * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
  * @version $Revision: 1 $
  */
-public interface FormAuthenticatorFactory extends ProviderFactory<FormAuthenticator> {
+public interface FormAuthenticatorFactory extends ProviderFactory<FormAuthenticator>, ConfigurableAuthenticatorFactory {
 }
diff --git a/services/src/main/java/org/keycloak/authentication/forms/RegistrationPage.java b/services/src/main/java/org/keycloak/authentication/forms/RegistrationPage.java
new file mode 100755
index 0000000..0fa38c5
--- /dev/null
+++ b/services/src/main/java/org/keycloak/authentication/forms/RegistrationPage.java
@@ -0,0 +1,116 @@
+package org.keycloak.authentication.forms;
+
+import org.keycloak.Config;
+import org.keycloak.OAuth2Constants;
+import org.keycloak.authentication.AuthenticatorContext;
+import org.keycloak.authentication.FormActionContext;
+import org.keycloak.authentication.FormAuthenticator;
+import org.keycloak.authentication.FormAuthenticatorFactory;
+import org.keycloak.login.LoginFormsProvider;
+import org.keycloak.models.AuthenticationExecutionModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakSessionFactory;
+import org.keycloak.models.UserCredentialModel;
+import org.keycloak.models.utils.FormMessage;
+import org.keycloak.services.resources.LoginActionsService;
+
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.core.Response;
+import java.net.URI;
+import java.util.List;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class RegistrationPage implements FormAuthenticator, FormAuthenticatorFactory {
+
+    public static final String EXECUTION = "execution";
+    public static final String FIELD_PASSWORD_CONFIRM = "password-confirm";
+    public static final String FIELD_PASSWORD = "password";
+    public static final String FIELD_EMAIL = "email";
+    public static final String FIELD_USERNAME = "username";
+    public static final String FIELD_LAST_NAME = "lastName";
+    public static final String FIELD_FIRST_NAME = "firstName";
+    public static final String PROVIDER_ID = "registration-page-form";
+
+    @Override
+    public void authenticate(AuthenticatorContext context) {
+        LoginFormsProvider registrationPage = createForm(context, context.getExecution().getId());
+        context.challenge(registrationPage.createRegistration());
+
+    }
+    public URI getActionUrl(AuthenticatorContext context, String executionId, String code) {
+        return LoginActionsService.registrationFormProcessor(context.getUriInfo())
+                .queryParam(OAuth2Constants.CODE, code)
+                .queryParam(EXECUTION, executionId)
+                .build(context.getRealm().getName());
+    }
+
+
+    @Override
+    public Response createChallenge(FormActionContext context, MultivaluedMap<String, String> formData, List<FormMessage> errorMessages) {
+        LoginFormsProvider registrationPage = createForm(context, context.getFormExecution().getId());
+        if (formData != null) registrationPage.setFormData(formData);
+        if (errorMessages != null) {
+            registrationPage.setErrors(errorMessages);
+        }
+        return registrationPage.createRegistration();
+    }
+
+    public LoginFormsProvider createForm(AuthenticatorContext context, String executionId) {
+        AuthenticationExecutionModel.Requirement categoryRequirement = context.getCategoryRequirementFromCurrentFlow(UserCredentialModel.PASSWORD);
+        boolean passwordRequired = categoryRequirement != null && categoryRequirement != AuthenticationExecutionModel.Requirement.DISABLED;
+        String code = context.generateAccessCode();
+        URI actionUrl = getActionUrl(context, executionId, code);
+        return context.getSession().getProvider(LoginFormsProvider.class)
+                .setAttribute("passwordRequired", passwordRequired)
+                .setActionUri(actionUrl)
+                .setClientSessionCode(code);
+    }
+
+    @Override
+    public void close() {
+
+    }
+
+    @Override
+    public String getDisplayType() {
+        return "Registration Page";
+    }
+
+    @Override
+    public String getReferenceCategory() {
+        return null;
+    }
+
+    @Override
+    public boolean isConfigurable() {
+        return false;
+    }
+
+    @Override
+    public AuthenticationExecutionModel.Requirement[] getRequirementChoices() {
+        return new AuthenticationExecutionModel.Requirement[0];
+    }
+
+    @Override
+    public FormAuthenticator create(KeycloakSession session) {
+        return this;
+    }
+
+    @Override
+    public void init(Config.Scope config) {
+
+    }
+
+    @Override
+    public void postInit(KeycloakSessionFactory factory) {
+
+    }
+
+    @Override
+    public String getId() {
+        return PROVIDER_ID;
+    }
+}
diff --git a/services/src/main/java/org/keycloak/authentication/forms/RegistrationPasswordValidation.java b/services/src/main/java/org/keycloak/authentication/forms/RegistrationPasswordValidation.java
new file mode 100755
index 0000000..c8df503
--- /dev/null
+++ b/services/src/main/java/org/keycloak/authentication/forms/RegistrationPasswordValidation.java
@@ -0,0 +1,117 @@
+package org.keycloak.authentication.forms;
+
+import org.keycloak.Config;
+import org.keycloak.authentication.AuthenticatorContext;
+import org.keycloak.authentication.FormAction;
+import org.keycloak.authentication.FormActionContext;
+import org.keycloak.authentication.FormActionFactory;
+import org.keycloak.authentication.FormAuthenticator;
+import org.keycloak.models.AuthenticationExecutionModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakSessionFactory;
+import org.keycloak.models.PasswordPolicy;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserCredentialModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.models.utils.FormMessage;
+import org.keycloak.services.messages.Messages;
+import org.keycloak.services.validation.Validation;
+
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.core.Response;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class RegistrationPasswordValidation implements FormAction, FormActionFactory {
+    public static final String PROVIDER_ID = "password-validation-action";
+
+    @Override
+    public void authenticate(FormActionContext context) {
+        MultivaluedMap<String, String> formData = context.getHttpRequest().getDecodedFormParameters();
+        List<FormMessage> errors = new ArrayList<>();
+        if (Validation.isBlank(formData.getFirst(RegistrationPage.FIELD_PASSWORD))) {
+            errors.add(new FormMessage(RegistrationPage.FIELD_PASSWORD, Messages.MISSING_PASSWORD));
+        } else if (!formData.getFirst(RegistrationPage.FIELD_PASSWORD).equals(formData.getFirst(RegistrationPage.FIELD_PASSWORD_CONFIRM))) {
+            errors.add(new FormMessage(RegistrationPage.FIELD_PASSWORD_CONFIRM, Messages.INVALID_PASSWORD_CONFIRM));
+        }
+        if (formData.getFirst(RegistrationPage.FIELD_PASSWORD) != null) {
+            PasswordPolicy.Error err = context.getRealm().getPasswordPolicy().validate(context.getRealm().isRegistrationEmailAsUsername() ? formData.getFirst(RegistrationPage.FIELD_EMAIL) : formData.getFirst(RegistrationPage.FIELD_USERNAME), formData.getFirst(RegistrationPage.FIELD_PASSWORD));
+            if (err != null)
+                errors.add(new FormMessage(RegistrationPage.FIELD_PASSWORD, err.getMessage(), err.getParameters()));
+        }
+
+        if (errors.size() > 0) {
+            formData.remove(RegistrationPage.FIELD_PASSWORD);
+            formData.remove(RegistrationPage.FIELD_PASSWORD_CONFIRM);
+            Response challenge = context.getFormAuthenticator().createChallenge(context, formData, errors);
+            context.challenge(challenge);
+            return;
+        } else {
+            context.success();
+        }
+    }
+
+    @Override
+    public boolean requiresUser() {
+        return false;
+    }
+
+    @Override
+    public boolean configuredFor(KeycloakSession session, RealmModel realm, UserModel user) {
+        return true;
+    }
+
+    @Override
+    public void setRequiredActions(KeycloakSession session, RealmModel realm, UserModel user) {
+
+    }
+
+    @Override
+    public void close() {
+
+    }
+
+    @Override
+    public String getDisplayType() {
+        return "Password Validation";
+    }
+
+    @Override
+    public String getReferenceCategory() {
+        return UserCredentialModel.PASSWORD;
+    }
+
+    @Override
+    public boolean isConfigurable() {
+        return false;
+    }
+
+    @Override
+    public AuthenticationExecutionModel.Requirement[] getRequirementChoices() {
+        return new AuthenticationExecutionModel.Requirement[0];
+    }
+
+    @Override
+    public FormAction create(KeycloakSession session) {
+        return this;
+    }
+
+    @Override
+    public void init(Config.Scope config) {
+
+    }
+
+    @Override
+    public void postInit(KeycloakSessionFactory factory) {
+
+    }
+
+    @Override
+    public String getId() {
+        return PROVIDER_ID;
+    }
+}
diff --git a/services/src/main/java/org/keycloak/authentication/forms/RegistrationProfileValidation.java b/services/src/main/java/org/keycloak/authentication/forms/RegistrationProfileValidation.java
new file mode 100755
index 0000000..9d0a11e
--- /dev/null
+++ b/services/src/main/java/org/keycloak/authentication/forms/RegistrationProfileValidation.java
@@ -0,0 +1,132 @@
+package org.keycloak.authentication.forms;
+
+import org.keycloak.Config;
+import org.keycloak.authentication.AuthenticatorContext;
+import org.keycloak.authentication.FormAction;
+import org.keycloak.authentication.FormActionContext;
+import org.keycloak.authentication.FormActionFactory;
+import org.keycloak.authentication.FormAuthenticator;
+import org.keycloak.events.Errors;
+import org.keycloak.models.AuthenticationExecutionModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakSessionFactory;
+import org.keycloak.models.PasswordPolicy;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.models.utils.FormMessage;
+import org.keycloak.services.messages.Messages;
+import org.keycloak.services.validation.Validation;
+
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.core.Response;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class RegistrationProfileValidation implements FormAction, FormActionFactory {
+    public static final String PROVIDER_ID = "profile-validation-action";
+
+
+    @Override
+    public void authenticate(FormActionContext context) {
+        MultivaluedMap<String, String> formData = context.getHttpRequest().getDecodedFormParameters();
+        List<FormMessage> errors = new ArrayList<>();
+
+        String eventError = Errors.INVALID_REGISTRATION;
+
+        if (Validation.isBlank(formData.getFirst((RegistrationPage.FIELD_FIRST_NAME)))) {
+            errors.add(new FormMessage(RegistrationPage.FIELD_FIRST_NAME, Messages.MISSING_FIRST_NAME));
+        }
+
+        if (Validation.isBlank(formData.getFirst((RegistrationPage.FIELD_LAST_NAME)))) {
+            errors.add(new FormMessage(RegistrationPage.FIELD_LAST_NAME, Messages.MISSING_LAST_NAME));
+        }
+
+        String email = formData.getFirst(Validation.FIELD_EMAIL);
+        if (Validation.isBlank(email)) {
+            errors.add(new FormMessage(RegistrationPage.FIELD_EMAIL, Messages.MISSING_EMAIL));
+        } else if (!Validation.isEmailValid(email)) {
+            formData.remove(Validation.FIELD_EMAIL);
+            errors.add(new FormMessage(RegistrationPage.FIELD_EMAIL, Messages.INVALID_EMAIL));
+        }
+
+        if (context.getSession().users().getUserByEmail(email, context.getRealm()) != null) {
+            eventError = Errors.EMAIL_IN_USE;
+            formData.remove(Validation.FIELD_EMAIL);
+            errors.add(new FormMessage(RegistrationPage.FIELD_EMAIL, Messages.EMAIL_EXISTS));
+        }
+
+        if (errors.size() > 0) {
+            context.getEvent().error(eventError);
+            Response challenge = context.getFormAuthenticator().createChallenge(context, formData, errors);
+            context.challenge(challenge);
+            return;
+
+        } else {
+            context.success();
+        }
+    }
+
+    @Override
+    public boolean requiresUser() {
+        return false;
+    }
+
+    @Override
+    public boolean configuredFor(KeycloakSession session, RealmModel realm, UserModel user) {
+        return true;
+    }
+
+    @Override
+    public void setRequiredActions(KeycloakSession session, RealmModel realm, UserModel user) {
+
+    }
+
+    @Override
+    public void close() {
+
+    }
+
+    @Override
+    public String getDisplayType() {
+        return "Profile Validation";
+    }
+
+    @Override
+    public String getReferenceCategory() {
+        return null;
+    }
+
+    @Override
+    public boolean isConfigurable() {
+        return false;
+    }
+
+    @Override
+    public AuthenticationExecutionModel.Requirement[] getRequirementChoices() {
+        return new AuthenticationExecutionModel.Requirement[0];
+    }
+
+    @Override
+    public FormAction create(KeycloakSession session) {
+        return this;
+    }
+
+    @Override
+    public void init(Config.Scope config) {
+
+    }
+
+    @Override
+    public void postInit(KeycloakSessionFactory factory) {
+
+    }
+
+    @Override
+    public String getId() {
+        return PROVIDER_ID;
+    }
+}
diff --git a/services/src/main/java/org/keycloak/authentication/forms/RegistrationUserCreation.java b/services/src/main/java/org/keycloak/authentication/forms/RegistrationUserCreation.java
new file mode 100755
index 0000000..60c28b2
--- /dev/null
+++ b/services/src/main/java/org/keycloak/authentication/forms/RegistrationUserCreation.java
@@ -0,0 +1,126 @@
+package org.keycloak.authentication.forms;
+
+import org.keycloak.Config;
+import org.keycloak.authentication.AuthenticatorContext;
+import org.keycloak.authentication.FormAction;
+import org.keycloak.authentication.FormActionContext;
+import org.keycloak.authentication.FormActionFactory;
+import org.keycloak.authentication.FormAuthenticator;
+import org.keycloak.login.LoginFormsProvider;
+import org.keycloak.models.AuthenticationExecutionModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakSessionFactory;
+import org.keycloak.models.ModelException;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserCredentialModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.protocol.oidc.OIDCLoginProtocol;
+import org.keycloak.representations.idm.CredentialRepresentation;
+import org.keycloak.services.resources.AttributeFormDataProcessor;
+import org.keycloak.services.validation.Validation;
+
+import javax.ws.rs.core.MultivaluedMap;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class RegistrationUserCreation implements FormAction, FormActionFactory {
+
+    public static final String PROVIDER_ID = "registration-user-creation";
+
+    @Override
+    public void authenticate(FormActionContext context) {
+        MultivaluedMap<String, String> formData = context.getHttpRequest().getDecodedFormParameters();
+        String email = formData.getFirst(Validation.FIELD_EMAIL);
+        String username = formData.getFirst(RegistrationPage.FIELD_USERNAME);
+        if (context.getRealm().isRegistrationEmailAsUsername()) {
+            username = formData.getFirst(RegistrationPage.FIELD_EMAIL);
+        }
+        UserModel user = context.getSession().users().addUser(context.getRealm(), username);
+        user.setEnabled(true);
+        user.setFirstName(formData.getFirst("firstName"));
+        user.setLastName(formData.getFirst("lastName"));
+
+        user.setEmail(email);
+        context.getClientSession().setNote(OIDCLoginProtocol.LOGIN_HINT_PARAM, username);
+        AttributeFormDataProcessor.process(formData, context.getRealm(), user);
+        context.setUser(user);
+        AuthenticationExecutionModel.Requirement categoryRequirement = context.getCategoryRequirementFromCurrentFlow(UserCredentialModel.PASSWORD);
+        boolean passwordRequired = categoryRequirement != null && categoryRequirement != AuthenticationExecutionModel.Requirement.DISABLED;
+        if (passwordRequired) {
+            String password = formData.getFirst(RegistrationPage.FIELD_PASSWORD);
+            UserCredentialModel credentials = new UserCredentialModel();
+            credentials.setType(CredentialRepresentation.PASSWORD);
+            credentials.setValue(password);
+
+            try {
+                context.getSession().users().updateCredential(context.getRealm(), user, UserCredentialModel.password(formData.getFirst("password")));
+            } catch (Exception me) {
+                user.addRequiredAction(UserModel.RequiredAction.UPDATE_PASSWORD);
+            }
+        }
+        context.getEvent().user(user);
+        context.success();
+    }
+
+    @Override
+    public boolean requiresUser() {
+        return false;
+    }
+
+    @Override
+    public boolean configuredFor(KeycloakSession session, RealmModel realm, UserModel user) {
+        return true;
+    }
+
+    @Override
+    public void setRequiredActions(KeycloakSession session, RealmModel realm, UserModel user) {
+
+    }
+
+    @Override
+    public void close() {
+
+    }
+
+    @Override
+    public String getDisplayType() {
+        return "Registration User Creation";
+    }
+
+    @Override
+    public String getReferenceCategory() {
+        return null;
+    }
+
+    @Override
+    public boolean isConfigurable() {
+        return false;
+    }
+
+    @Override
+    public AuthenticationExecutionModel.Requirement[] getRequirementChoices() {
+        return new AuthenticationExecutionModel.Requirement[0];
+    }
+
+    @Override
+    public FormAction create(KeycloakSession session) {
+        return this;
+    }
+
+    @Override
+    public void init(Config.Scope config) {
+
+    }
+
+    @Override
+    public void postInit(KeycloakSessionFactory factory) {
+
+    }
+
+    @Override
+    public String getId() {
+        return PROVIDER_ID;
+    }
+}
diff --git a/services/src/main/java/org/keycloak/authentication/forms/RegistrationUsernameValidation.java b/services/src/main/java/org/keycloak/authentication/forms/RegistrationUsernameValidation.java
new file mode 100755
index 0000000..fa5e8e4
--- /dev/null
+++ b/services/src/main/java/org/keycloak/authentication/forms/RegistrationUsernameValidation.java
@@ -0,0 +1,147 @@
+package org.keycloak.authentication.forms;
+
+import org.keycloak.Config;
+import org.keycloak.authentication.AuthenticatorContext;
+import org.keycloak.authentication.FormAction;
+import org.keycloak.authentication.FormActionContext;
+import org.keycloak.authentication.FormActionFactory;
+import org.keycloak.authentication.FormAuthenticator;
+import org.keycloak.events.Errors;
+import org.keycloak.models.AuthenticationExecutionModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakSessionFactory;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.models.utils.FormMessage;
+import org.keycloak.services.messages.Messages;
+import org.keycloak.services.validation.Validation;
+
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.core.Response;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class RegistrationUsernameValidation implements FormAction, FormActionFactory {
+
+
+    public static final String PROVIDER_ID = "username-validation-action";
+
+    @Override
+    public void authenticate(FormActionContext context) {
+        MultivaluedMap<String, String> formData = context.getHttpRequest().getDecodedFormParameters();
+        List<FormMessage> errors = new ArrayList<>();
+
+        String email = formData.getFirst(Validation.FIELD_EMAIL);
+        String username = formData.getFirst(RegistrationPage.FIELD_USERNAME);
+
+        String usernameField = RegistrationPage.FIELD_USERNAME;
+        if (context.getRealm().isRegistrationEmailAsUsername()) {
+            username = email;
+            usernameField = RegistrationPage.FIELD_EMAIL;
+            if (Validation.isBlank(email)) {
+                errors.add(new FormMessage(RegistrationPage.FIELD_EMAIL, Messages.MISSING_EMAIL));
+            } else if (!Validation.isEmailValid(email)) {
+                errors.add(new FormMessage(RegistrationPage.FIELD_EMAIL, Messages.INVALID_EMAIL));
+                formData.remove(Validation.FIELD_EMAIL);
+            }
+            if (errors.size() > 0) {
+                context.getEvent().error(Errors.INVALID_REGISTRATION);
+                Response challenge = context.getFormAuthenticator().createChallenge(context, formData, errors);
+                context.challenge(challenge);
+                return;
+            }
+            if (email != null && context.getSession().users().getUserByEmail(email, context.getRealm()) != null) {
+                context.getEvent().error(Errors.EMAIL_IN_USE);
+                formData.remove(Validation.FIELD_EMAIL);
+                errors.add(new FormMessage(RegistrationPage.FIELD_EMAIL, Messages.EMAIL_EXISTS));
+                Response challenge = context.getFormAuthenticator().createChallenge(context, formData, errors);
+                context.challenge(challenge);
+                return;
+            }
+        } else {
+            if (Validation.isBlank(username)) {
+                context.getEvent().error(Errors.INVALID_REGISTRATION);
+                errors.add(new FormMessage(RegistrationPage.FIELD_USERNAME, Messages.MISSING_USERNAME));
+                Response challenge = context.getFormAuthenticator().createChallenge(context, formData, errors);
+                context.challenge(challenge);
+                return;
+            }
+
+        }
+        if (context.getSession().users().getUserByUsername(username, context.getRealm()) != null) {
+            context.getEvent().error(Errors.USERNAME_IN_USE);
+            errors.add(new FormMessage(usernameField, Messages.USERNAME_EXISTS));
+            formData.remove(Validation.FIELD_USERNAME);
+            formData.remove(Validation.FIELD_EMAIL);
+            Response challenge = context.getFormAuthenticator().createChallenge(context, formData, errors);
+            context.challenge(challenge);
+            return;
+
+        }
+        context.success();
+    }
+
+    @Override
+    public boolean requiresUser() {
+        return false;
+    }
+
+    @Override
+    public boolean configuredFor(KeycloakSession session, RealmModel realm, UserModel user) {
+        return true;
+    }
+
+    @Override
+    public void setRequiredActions(KeycloakSession session, RealmModel realm, UserModel user) {
+
+    }
+
+    @Override
+    public void close() {
+
+    }
+
+    @Override
+    public String getDisplayType() {
+        return "Username Validation";
+    }
+
+    @Override
+    public String getReferenceCategory() {
+        return null;
+    }
+
+    @Override
+    public boolean isConfigurable() {
+        return false;
+    }
+
+    @Override
+    public AuthenticationExecutionModel.Requirement[] getRequirementChoices() {
+        return new AuthenticationExecutionModel.Requirement[0];
+    }
+
+    @Override
+    public FormAction create(KeycloakSession session) {
+        return this;
+    }
+
+    @Override
+    public void init(Config.Scope config) {
+
+    }
+
+    @Override
+    public void postInit(KeycloakSessionFactory factory) {
+
+    }
+
+    @Override
+    public String getId() {
+        return PROVIDER_ID;
+    }
+}
diff --git a/services/src/main/java/org/keycloak/services/DefaultKeycloakSession.java b/services/src/main/java/org/keycloak/services/DefaultKeycloakSession.java
index 190750f..c672235 100755
--- a/services/src/main/java/org/keycloak/services/DefaultKeycloakSession.java
+++ b/services/src/main/java/org/keycloak/services/DefaultKeycloakSession.java
@@ -29,7 +29,7 @@ import java.util.Set;
 public class DefaultKeycloakSession implements KeycloakSession {
 
     private final DefaultKeycloakSessionFactory factory;
-    private final Map<Integer, Provider> providers = new HashMap<Integer, Provider>();
+    private final Map<Integer, Provider> providers = new HashMap<>();
     private final List<Provider> closable = new LinkedList<Provider>();
     private final DefaultKeycloakTransactionManager transactionManager;
     private RealmProvider model;
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/AuthenticationManagementResource.java b/services/src/main/java/org/keycloak/services/resources/admin/AuthenticationManagementResource.java
index 7f1edbd..a78baaa 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/AuthenticationManagementResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/AuthenticationManagementResource.java
@@ -8,7 +8,6 @@ import org.keycloak.authentication.AuthenticatorFactory;
 import org.keycloak.authentication.AuthenticatorUtil;
 import org.keycloak.models.AuthenticationExecutionModel;
 import org.keycloak.models.AuthenticationFlowModel;
-import org.keycloak.models.AuthenticatorConfigModel;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.RequiredActionProviderModel;
@@ -139,8 +138,8 @@ public class AuthenticationManagementResource {
                     rep.setSubFlow(true);
                 }
                 AuthenticatorFactory factory = (AuthenticatorFactory)session.getKeycloakSessionFactory().getProviderFactory(Authenticator.class, execution.getAuthenticator());
-                if (factory.getReferenceType() == null) continue;
-                rep.setReferenceType(factory.getReferenceType());
+                if (factory.getReferenceCategory() == null) continue;
+                rep.setReferenceType(factory.getReferenceCategory());
                 rep.setConfigurable(factory.isConfigurable());
                 for (AuthenticationExecutionModel.Requirement choice : factory.getRequirementChoices()) {
                     rep.getRequirementChoices().add(choice.name());
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 44910cb..a9db3a0 100755
--- a/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java
+++ b/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java
@@ -130,6 +130,10 @@ public class LoginActionsService {
         return loginActionsBaseUrl(uriInfo).path(LoginActionsService.class, "authenticateForm");
     }
 
+    public static UriBuilder registrationFormProcessor(UriInfo uriInfo) {
+        return loginActionsBaseUrl(uriInfo).path(LoginActionsService.class, "processRegister");
+    }
+
     public static UriBuilder loginActionsBaseUrl(UriBuilder baseUriBuilder) {
         return baseUriBuilder.path(RealmsResource.class).path(RealmsResource.class, "getLoginActionsService");
     }
@@ -269,6 +273,10 @@ public class LoginActionsService {
 
     protected Response processAuthentication(String execution, ClientSessionModel clientSession) {
         String flowAlias = DefaultAuthenticationFlows.BROWSER_FLOW;
+        return processFlow(execution, clientSession, flowAlias);
+    }
+
+    protected Response processFlow(String execution, ClientSessionModel clientSession, String flowAlias) {
         AuthenticationFlowModel flow = realm.getFlowByAlias(flowAlias);
         AuthenticationProcessor processor = new AuthenticationProcessor();
         processor.setClientSession(clientSession)
@@ -313,6 +321,12 @@ public class LoginActionsService {
         return processAuthentication(execution, clientSession);
     }
 
+    protected Response processRegistration(String execution, ClientSessionModel clientSession) {
+        String flowAlias = DefaultAuthenticationFlows.REGISTRATION_FLOW;
+        return processFlow(execution, clientSession, flowAlias);
+    }
+
+
 
     /**
      * protocol independent registration page entry point
@@ -322,7 +336,8 @@ public class LoginActionsService {
      */
     @Path("registration")
     @GET
-    public Response registerPage(@QueryParam("code") String code) {
+    public Response registerPage(@QueryParam("code") String code,
+                                 @QueryParam("execution") String execution) {
         event.event(EventType.REGISTER);
         if (!realm.isRegistrationAllowed()) {
             event.error(Errors.REGISTRATION_DISABLED);
@@ -330,7 +345,7 @@ public class LoginActionsService {
         }
 
         Checks checks = new Checks();
-        if (!checks.check(code)) {
+        if (!checks.check(code, ClientSessionModel.Action.AUTHENTICATE.name())) {
             return checks.response;
         }
         event.detail(Details.CODE_ID, code);
@@ -340,10 +355,7 @@ public class LoginActionsService {
 
         authManager.expireIdentityCookie(realm, uriInfo, clientConnection);
 
-        return session.getProvider(LoginFormsProvider.class)
-                .setClientSessionCode(clientSessionCode.getCode())
-                .setAttribute("passwordRequired", isPasswordRequired())
-                .createRegistration();
+        return processRegistration(execution, clientSession);
     }
 
 
@@ -353,20 +365,27 @@ public class LoginActionsService {
      * @param code
      * @return
      */
-    @Path("request/registration")
+    @Path("registration")
     @POST
-    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
-    public Response processRegister(@QueryParam("code") String code) {
+    public Response processRegister(@QueryParam("code") String code,
+                                    @QueryParam("execution") String execution) {
         event.event(EventType.REGISTER);
         Checks checks = new Checks();
         if (!checks.check(code, ClientSessionModel.Action.AUTHENTICATE.name())) {
             return checks.response;
         }
-         if (!realm.isRegistrationAllowed()) {
+        if (!realm.isRegistrationAllowed()) {
             event.error(Errors.REGISTRATION_DISABLED);
             return ErrorPage.error(session, Messages.REGISTRATION_NOT_ALLOWED);
         }
 
+        ClientSessionCode clientCode = checks.clientCode;
+        ClientSessionModel clientSession = clientCode.getClientSession();
+
+        return processRegistration(execution, clientSession);
+    }
+
+    public Response oldRegistration(ClientSessionCode clientCode, ClientSessionModel clientSession) {
         MultivaluedMap<String, String> formData = request.getDecodedFormParameters();
         String username = formData.getFirst(Validation.FIELD_USERNAME);
         String email = formData.getFirst(Validation.FIELD_EMAIL);
@@ -374,8 +393,6 @@ public class LoginActionsService {
             username = email;
             formData.putSingle(AuthenticationManager.FORM_USERNAME, username);
         }
-        ClientSessionCode clientCode = checks.clientCode;
-        ClientSessionModel clientSession = clientCode.getClientSession();
         event.client(clientSession.getClient())
                 .detail(Details.REDIRECT_URI, clientSession.getRedirectUri())
                 .detail(Details.RESPONSE_TYPE, "code")
@@ -454,11 +471,11 @@ public class LoginActionsService {
 
             // User already registered, but force him to update password
             if (!passwordUpdateSuccessful) {
-                user.addRequiredAction(UserModel.RequiredAction.UPDATE_PASSWORD);
+                user.addRequiredAction(RequiredAction.UPDATE_PASSWORD);
                 return session.getProvider(LoginFormsProvider.class)
                         .setError(passwordUpdateError, passwordUpdateErrorParameters)
                         .setClientSessionCode(clientCode.getCode())
-                        .createResponse(UserModel.RequiredAction.UPDATE_PASSWORD);
+                        .createResponse(RequiredAction.UPDATE_PASSWORD);
             }
         }
         clientSession.setNote(OIDCLoginProtocol.LOGIN_HINT_PARAM, username);
diff --git a/services/src/main/resources/META-INF/services/org.keycloak.authentication.FormActionFactory b/services/src/main/resources/META-INF/services/org.keycloak.authentication.FormActionFactory
new file mode 100755
index 0000000..47a023c
--- /dev/null
+++ b/services/src/main/resources/META-INF/services/org.keycloak.authentication.FormActionFactory
@@ -0,0 +1,4 @@
+org.keycloak.authentication.forms.RegistrationPasswordValidation
+org.keycloak.authentication.forms.RegistrationProfileValidation
+org.keycloak.authentication.forms.RegistrationUserCreation
+org.keycloak.authentication.forms.RegistrationUsernameValidation
\ No newline at end of file
diff --git a/services/src/main/resources/META-INF/services/org.keycloak.authentication.FormAuthenticatorFactory b/services/src/main/resources/META-INF/services/org.keycloak.authentication.FormAuthenticatorFactory
new file mode 100755
index 0000000..4be1af4
--- /dev/null
+++ b/services/src/main/resources/META-INF/services/org.keycloak.authentication.FormAuthenticatorFactory
@@ -0,0 +1 @@
+org.keycloak.authentication.forms.RegistrationPage
\ No newline at end of file
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/account/AccountTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/account/AccountTest.java
index d621e46..b97a9d3 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/account/AccountTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/account/AccountTest.java
@@ -166,7 +166,7 @@ public class AccountTest {
         });
     }
 
-    //@Test
+    @Test
     public void ideTesting() throws Exception {
         Thread.sleep(100000000);
     }
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/utils/CredentialHelper.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/utils/CredentialHelper.java
index 557fa7e..6a03f3e 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/utils/CredentialHelper.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/utils/CredentialHelper.java
@@ -5,7 +5,6 @@ import org.keycloak.authentication.authenticators.SpnegoAuthenticatorFactory;
 import org.keycloak.authentication.authenticators.UsernamePasswordFormFactory;
 import org.keycloak.models.AuthenticationExecutionModel;
 import org.keycloak.models.AuthenticationFlowModel;
-import org.keycloak.models.AuthenticatorConfigModel;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.utils.DefaultAuthenticationFlows;
 import org.keycloak.representations.idm.CredentialRepresentation;
@@ -29,7 +28,7 @@ public class CredentialHelper {
     public static void setCredentialRequirement(String type, RealmModel realm, AuthenticationExecutionModel.Requirement requirement) {
         if (type.equals(CredentialRepresentation.TOTP)) {
             String providerId = OTPFormAuthenticatorFactory.PROVIDER_ID;
-            String flowAlias = DefaultAuthenticationFlows.FORMS_FLOW;
+            String flowAlias = DefaultAuthenticationFlows.LOGIN_FORMS_FLOW;
             authenticationRequirement(realm, providerId, flowAlias, requirement);
         } else if (type.equals(CredentialRepresentation.KERBEROS)) {
             String providerId = SpnegoAuthenticatorFactory.PROVIDER_ID;
@@ -37,7 +36,7 @@ public class CredentialHelper {
             authenticationRequirement(realm, providerId, flowAlias, requirement);
         } else if (type.equals(CredentialRepresentation.PASSWORD)) {
             String providerId = UsernamePasswordFormFactory.PROVIDER_ID;
-            String flowAlias = DefaultAuthenticationFlows.FORMS_FLOW;
+            String flowAlias = DefaultAuthenticationFlows.LOGIN_FORMS_FLOW;
             authenticationRequirement(realm, providerId, flowAlias, requirement);
         }
     }