keycloak-uncached

stuff

8/13/2015 4:32:58 PM

Changes

Details

diff --git a/docbook/reference/en/en-US/modules/auth-spi.xml b/docbook/reference/en/en-US/modules/auth-spi.xml
index 05e7f42..29c8ce1 100755
--- a/docbook/reference/en/en-US/modules/auth-spi.xml
+++ b/docbook/reference/en/en-US/modules/auth-spi.xml
@@ -370,9 +370,9 @@ Forms Subflow - ALTERNATIVE
                 the Authenticator.
             </para>
             <para>
-                The getId() method is just the unique name of the component.  The create() methods should also
-                be self explanatory.  The create(KeycloakSession) method will actually never be called.  It is just
-                an artifact of the more generic ProviderFactory interface.
+                The getId() method is just the unique name of the component.  The create() method is called by the
+                runtime to allocate and process the Authenticator.
+
 <programlisting>
 public class SecretQuestionAuthenticatorFactory implements AuthenticatorFactory, ConfigurableAuthenticatorFactory {
 
@@ -385,11 +385,6 @@ public class SecretQuestionAuthenticatorFactory implements AuthenticatorFactory,
     }
 
     @Override
-    public Authenticator create() {
-        return SINGLETON;
-    }
-
-    @Override
     public Authenticator create(KeycloakSession session) {
         return SINGLETON;
     }
@@ -523,4 +518,253 @@ public class SecretQuestionAuthenticatorFactory implements AuthenticatorFactory,
             </para>
         </section>
     </section>
+    <section>
+        <title>Required Action Walkthrough</title>
+        <para>
+            In this section will discuss how to define a required action.  In the Authenticator section you may have wondered,
+            "How will we get the user's answer to the secret question entered into the system?".  As we showed in the example,
+            if the answer is not set up, a required action will be triggered.  This section discusses how to implement
+            the required action for the Secret Question Authenticator.
+        </para>
+        <section>
+            <title>Packaging Classes and Deployment</title>
+            <para>
+                You will package your classes within a single jar.  This jar does not have to be separate from other provider classes
+                but it must contain a file named  <literal>org.keycloak.authentication.RequiredActionFactory</literal>
+                and must be contained in the <literal>META-INF/services/</literal> directory of your jar.  This file must list the fully qualified classname
+                of each RequiredActionFactory implementation you have in the jar.  For example:
+                <programlisting>
+                    org.keycloak.examples.authenticator.SecretQuestionRequiredActionFactory
+                </programlisting>
+            </para>
+            <para>
+                This services/ file is used by Keycloak to scan the providers it has to load into the system.
+            </para>
+            <para>
+                To deploy this jar, just copy it to the standalone/configuration/providers directory.
+            </para>
+        </section>
+        <section>
+            <title>Implement the RequiredActionProvider</title>
+            <para>Required actions must first implement the RequiredActionProvider interface.  The RequiredActionProvider.requiredActionChallenge()
+            is the initial call by the flow manager into the required action.  This method is responsible for rendering the
+            HTML form that will drive the required action.
+<programlisting>
+    @Override
+    public void requiredActionChallenge(RequiredActionContext context) {
+        Response challenge = context.form().createForm("secret_question_config.ftl");
+        context.challenge(challenge);
+
+    }
+</programlisting>
+            </para>
+            <para>
+                You see that RequiredActionContext has similar methods to AuthenticationFlowContext.  The form() method allows
+                you to render the page from a Freemarker template.  The action URL is preset by the call to this form() method.
+                You just need to reference it within your HTML form.  I'll show you this later.
+            </para>
+            <para>
+                The challenge() method notifies the flow manager that
+                a required action must be executed.
+            </para>
+            <para>
+                The next method is responsible for processing input from the HTML form of the required action.  The action
+                URL of the form will be routed to the RequiredActionProvider.processAction() method
+<programlisting>
+    @Override
+    public void processAction(RequiredActionContext context) {
+        String answer = (context.getHttpRequest().getDecodedFormParameters().getFirst("answer"));
+        UserCredentialValueModel model = new UserCredentialValueModel();
+        model.setValue(answer);
+        model.setType(SecretQuestionAuthenticator.CREDENTIAL_TYPE);
+        context.getUser().updateCredentialDirectly(model);
+        context.success();
+    }
+</programlisting>
+            </para>
+            <para>
+                The answer is pulled out of the form post.  a UserCredentialValueModel is created and the type and value
+                of the credential are set.  Then UserModel.updateCredentialDirectly() is invoked.  Finally, RequiredActionContext.success()
+                notifies the container that the required action was successful.
+            </para>
+        </section>
+        <section>
+            <title>Implement the RequiredActionFactory</title>
+            <para>
+                This class is really simple.  It is just responsible for creating the required actin provider instance.
+<programlisting>
+public class SecretQuestionRequiredActionFactory implements RequiredActionFactory {
+
+    private static final SecretQuestionRequiredAction SINGLETON = new SecretQuestionRequiredAction();
+
+    @Override
+    public RequiredActionProvider create(KeycloakSession session) {
+        return SINGLETON;
+    }
+
+
+    @Override
+    public String getId() {
+        return SecretQuestionRequiredAction.PROVIDER_ID;
+    }
+
+    @Override
+    public String getDisplayText() {
+        return "Secret Question";
+    }
+
+</programlisting>
+            </para>
+            <para>
+                The getDisplayText() method is just for the admin console when it wants to display a friendly name
+                for the required action.
+            </para>
+        </section>
+        <section>
+            <title>Enable Required Action</title>
+            <para>
+                The final thing you have to do is go into the admin console.  Click on the Authentication left menu.
+                Click on the Required Actions tab.  Find your required action, and enable.  Alternatively, if you
+                click on the default action checkbox, this required action will be applied anytime a new user is created.
+            </para>
+        </section>
+    </section>
+    <section>
+        <title>Modifying/Extending the Registration Form</title>
+        <para>
+            It is entirely possible for you to implement your own flow with a set of Authenticators to totally change
+            how regisration is done in Keycloak.  But what you'll usually want to do is just add a little be of validation
+            to the out of the box registration page.
+            An additional SPI was created to be able to do this.  It basically allows
+            you to add validation of form elements on the page as well as to initialize UserModel attributes and data
+            after the user has been registered.  We'll look at the implementation of the recaptcha support that
+            Keycloak provides out of the box to show you how to do this.
+        </para>
+    <section>
+        <title>Implementation FormAction Interface</title>
+        <para>
+            The core interface you have to implement is the FormAction interface.  A FormAction is responsible for
+            rendering and processing a portion of the page.  Rendering is done in the buildPage() method, validation
+            is done in the validate() method, post validation operations are done in success().  Let's first take a look
+            at buildPage()
+<programlisting><![CDATA[
+    @Override
+    public void buildPage(FormContext context, LoginFormsProvider form) {
+        AuthenticatorConfigModel captchaConfig = context.getAuthenticatorConfig();
+        if (captchaConfig == null || captchaConfig.getConfig() == null
+                || captchaConfig.getConfig().get(SITE_KEY) == null
+                || captchaConfig.getConfig().get(SITE_SECRET) == null
+                ) {
+            form.addError(new FormMessage(null, Messages.RECAPTCHA_NOT_CONFIGURED));
+            return;
+        }
+        String siteKey = captchaConfig.getConfig().get(SITE_KEY);
+        form.setAttribute("recaptchaRequired", true);
+        form.setAttribute("recaptchaSiteKey", siteKey);
+        form.addScript("https://www.google.com/recaptcha/api.js");
+    }
+]]>
+</programlisting>
+        </para>
+        <para>
+            The buildPage() method is a callback by the form flow to help render the page.  It receives a form parameter
+            which is a LoginFormsProvider.  You can add additional attributes to the form provider so that they can
+            be displayed in the HTML page generated by the registration Freemarker template.
+        </para>
+        <para>
+            The code above is from the registration recaptcha plugin.  Recaptcha requires some specific settings that
+            must be obtained from configuration.  FormActions are configured in the exact same was as Authenticators are.
+            In this example, we pull the Google Recaptcha site key from configuration and add it as an attribute
+            to the form provider.  Our regstration template file can read this attribute now.
+        </para>
+        <para>
+            Recaptcha also has the requirement of loading a javascript script.  You can do this by calling LoginFormsProvider.addScript()
+            passing in the URL.
+        </para>
+        <para>
+            The next meaty part of this interface is the validate() method.  This is called immediately upon receiving a form
+            post.
+<programlisting><![CDATA[
+    @Override
+    public void validate(ValidationContext context) {
+        MultivaluedMap<String, String> formData = context.getHttpRequest().getDecodedFormParameters();
+        List<FormMessage> errors = new ArrayList<>();
+        boolean success = false;
+
+        String captcha = formData.getFirst(G_RECAPTCHA_RESPONSE);
+        if (!Validation.isBlank(captcha)) {
+            AuthenticatorConfigModel captchaConfig = context.getAuthenticatorConfig();
+            String secret = captchaConfig.getConfig().get(SITE_SECRET);
+
+            success = validateRecaptcha(context, success, captcha, secret);
+        }
+        if (success) {
+            context.success();
+        } else {
+            errors.add(new FormMessage(null, Messages.RECAPTCHA_FAILED));
+            formData.remove(G_RECAPTCHA_RESPONSE);
+            context.validationError(formData, errors);
+            return;
+
+
+        }
+    }
+]]>
+</programlisting>
+        </para>
+        <para>
+            Here we obtain the form data that the Recaptcha widget adds to the form.  We obtain the Recaptcha secret key
+            from configuration.  We then validate the recaptcha.  If successful, ValidationContext.success() is called.
+            If not, we invoke ValidationContext.validationError() passing in the formData (so the user doesn't have to re-enter data),
+            we also specify an error message we want displayed.  The error message must point to a message bundle property
+            in the internationalized message bundles.  For other registration extensions validate() might be validating the
+            format of a form element, i.e. an alternative email attribute.
+        </para>
+        <para>
+            After all validations have been processed then, the form flow then invokes the FormAction.success() method.  For recaptcha
+            this is a no-op, but if you have additional metadata you want to add to UserModel, you can do that in success() method.
+        </para>
+        <para>
+            Finally the FormActionFactory class is really implemented similarly to AuthenticatorFactory, so we won't go over it.
+        </para>
+    </section>
+        <section>
+            <title>Packaging the Action</title>
+            <para>
+                You will package your classes within a single jar.  This jar must contain a file named  <literal>org.keycloak.authentication.ForActionFactory</literal>
+                and must be contained in the <literal>META-INF/services/</literal> directory of your jar.  This file must list the fully qualified classname
+                of each FormActionFactory implementation you have in the jar.  For example:
+                <programlisting>
+                    org.keycloak.examples.authenticator.registration.RecaptchaFormActionFactory
+                </programlisting>
+            </para>
+            <para>
+                This services/ file is used by Keycloak to scan the providers it has to load into the system.
+            </para>
+            <para>
+                To deploy this jar, just copy it to the standalone/configuration/providers directory.
+            </para>
+        </section>
+        <section>
+            <title>Adding FormAction to the Registration Flow</title>
+            <para>
+                Adding an FormAction to a registration page flow must be done in the admin console.
+                If you go to the Authentication menu item and go to the Flow tab, you will be able to view the currently
+                defined flows.  You cannot modify an built in flows, so, to add the Authenticator we've created you
+                have to copy an existing flow or create your own.  I'm hoping the UI is intuitive enough so that you
+                can figure out for yourself how to create a flow and add the FormAction.
+            </para>
+            <para>
+                Basically you'll have to copy the registration flow.  Then click Actions menu to the right of
+                the Registration Form, and pick "Add Execution" to add a new execution.  You'll pick the FormAction from the selection list.
+                Make sure your FormAction comes after "Registration User Creation" by using the down errors to move it if your FormAction
+                isn't already listed after "Registration User Creation".
+            </para>
+            <para>
+                After you've created your flow, you have to bind it to registration.  If you go
+                to the Authentication menu and go  to the Bindings tab you will see options to bind a flow to
+                the browser, registration, or direct grant flow.
+            </para>
+        </section>
+    </section>
 </chapter>
\ No newline at end of file
diff --git a/examples/providers/authenticator/src/main/java/org/keycloak/examples/authenticator/SecretQuestionAuthenticatorFactory.java b/examples/providers/authenticator/src/main/java/org/keycloak/examples/authenticator/SecretQuestionAuthenticatorFactory.java
index 09be4c1..94db2ae 100755
--- a/examples/providers/authenticator/src/main/java/org/keycloak/examples/authenticator/SecretQuestionAuthenticatorFactory.java
+++ b/examples/providers/authenticator/src/main/java/org/keycloak/examples/authenticator/SecretQuestionAuthenticatorFactory.java
@@ -27,11 +27,6 @@ public class SecretQuestionAuthenticatorFactory implements AuthenticatorFactory,
     }
 
     @Override
-    public Authenticator create() {
-        return SINGLETON;
-    }
-
-    @Override
     public Authenticator create(KeycloakSession session) {
         return SINGLETON;
     }
diff --git a/examples/providers/authenticator/src/main/java/org/keycloak/examples/authenticator/SecretQuestionRequiredAction.java b/examples/providers/authenticator/src/main/java/org/keycloak/examples/authenticator/SecretQuestionRequiredAction.java
index 6644b8e..d0e2de7 100755
--- a/examples/providers/authenticator/src/main/java/org/keycloak/examples/authenticator/SecretQuestionRequiredAction.java
+++ b/examples/providers/authenticator/src/main/java/org/keycloak/examples/authenticator/SecretQuestionRequiredAction.java
@@ -36,11 +36,6 @@ public class SecretQuestionRequiredAction implements RequiredActionProvider {
     }
 
     @Override
-    public String getProviderId() {
-        return PROVIDER_ID;
-    }
-
-    @Override
     public void close() {
 
     }
diff --git a/examples/providers/authenticator/src/main/resources/META-INF/services/org.keycloak.authentication.RequiredActionFactory b/examples/providers/authenticator/src/main/resources/META-INF/services/org.keycloak.authentication.RequiredActionFactory
new file mode 100755
index 0000000..88b6a06
--- /dev/null
+++ b/examples/providers/authenticator/src/main/resources/META-INF/services/org.keycloak.authentication.RequiredActionFactory
@@ -0,0 +1 @@
+org.keycloak.examples.authenticator.SecretQuestionRequiredActionFactory
\ No newline at end of file
diff --git a/forms/login-api/src/main/java/org/keycloak/login/LoginFormsProvider.java b/forms/login-api/src/main/java/org/keycloak/login/LoginFormsProvider.java
index 745e395..039a5bf 100755
--- a/forms/login-api/src/main/java/org/keycloak/login/LoginFormsProvider.java
+++ b/forms/login-api/src/main/java/org/keycloak/login/LoginFormsProvider.java
@@ -23,6 +23,13 @@ import org.keycloak.provider.Provider;
  */
 public interface LoginFormsProvider extends Provider {
 
+    /**
+     * Adds a script to the html header
+     *
+     * @param scriptUrl
+     */
+    void addScript(String scriptUrl);
+
     public Response createResponse(UserModel.RequiredAction action);
 
     Response createForm(String form);
diff --git a/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/FreeMarkerLoginFormsProvider.java b/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/FreeMarkerLoginFormsProvider.java
index 12c27d4..0b74d1e 100755
--- a/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/FreeMarkerLoginFormsProvider.java
+++ b/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/FreeMarkerLoginFormsProvider.java
@@ -91,6 +91,13 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
     public FreeMarkerLoginFormsProvider(KeycloakSession session, FreeMarkerUtil freeMarker) {
         this.session = session;
         this.freeMarker = freeMarker;
+        this.attributes.put("scripts", new LinkedList<String>());
+    }
+
+    @Override
+    public void addScript(String scriptUrl) {
+        List<String> scripts = (List<String>)this.attributes.get("scripts");
+        scripts.add(scriptUrl);
     }
 
     public Response createResponse(UserModel.RequiredAction action) {
diff --git a/services/src/main/java/org/keycloak/authentication/AuthenticatorFactory.java b/services/src/main/java/org/keycloak/authentication/AuthenticatorFactory.java
index ccc854b..cd0c727 100755
--- a/services/src/main/java/org/keycloak/authentication/AuthenticatorFactory.java
+++ b/services/src/main/java/org/keycloak/authentication/AuthenticatorFactory.java
@@ -16,6 +16,4 @@ import org.keycloak.provider.ProviderFactory;
 * @version $Revision: 1 $
 */
 public interface AuthenticatorFactory extends ProviderFactory<Authenticator>, ConfigurableAuthenticatorFactory {
-    Authenticator create();
-
 }
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/browser/CookieAuthenticatorFactory.java b/services/src/main/java/org/keycloak/authentication/authenticators/browser/CookieAuthenticatorFactory.java
index 4845b12..76b6af1 100755
--- a/services/src/main/java/org/keycloak/authentication/authenticators/browser/CookieAuthenticatorFactory.java
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/browser/CookieAuthenticatorFactory.java
@@ -17,14 +17,10 @@ import java.util.List;
 public class CookieAuthenticatorFactory implements AuthenticatorFactory {
     public static final String PROVIDER_ID = "auth-cookie";
     static CookieAuthenticator SINGLETON = new CookieAuthenticator();
-    @Override
-    public Authenticator create() {
-        return SINGLETON;
-    }
 
     @Override
     public Authenticator create(KeycloakSession session) {
-        throw new IllegalStateException("illegal call");
+        return SINGLETON;
     }
 
     @Override
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/browser/OTPFormAuthenticatorFactory.java b/services/src/main/java/org/keycloak/authentication/authenticators/browser/OTPFormAuthenticatorFactory.java
index 1a01e5a..9f76dec 100755
--- a/services/src/main/java/org/keycloak/authentication/authenticators/browser/OTPFormAuthenticatorFactory.java
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/browser/OTPFormAuthenticatorFactory.java
@@ -18,15 +18,11 @@ import java.util.List;
 public class OTPFormAuthenticatorFactory implements AuthenticatorFactory {
 
     public static final String PROVIDER_ID = "auth-otp-form";
-
-    @Override
-    public Authenticator create() {
-        return new OTPFormAuthenticator();
-    }
+    public static final OTPFormAuthenticator SINGLETON = new OTPFormAuthenticator();
 
     @Override
     public Authenticator create(KeycloakSession session) {
-        throw new IllegalStateException("illegal call");
+        return SINGLETON;
     }
 
     @Override
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/browser/SpnegoAuthenticatorFactory.java b/services/src/main/java/org/keycloak/authentication/authenticators/browser/SpnegoAuthenticatorFactory.java
index ecfa068..f5c7408 100755
--- a/services/src/main/java/org/keycloak/authentication/authenticators/browser/SpnegoAuthenticatorFactory.java
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/browser/SpnegoAuthenticatorFactory.java
@@ -18,15 +18,11 @@ import java.util.List;
 public class SpnegoAuthenticatorFactory implements AuthenticatorFactory {
 
     public static final String PROVIDER_ID = "auth-spnego";
-
-    @Override
-    public Authenticator create() {
-        return new SpnegoAuthenticator();
-    }
+    public static final SpnegoAuthenticator SINGLETON = new SpnegoAuthenticator();
 
     @Override
     public Authenticator create(KeycloakSession session) {
-        throw new IllegalStateException("illegal call");
+        return SINGLETON;
     }
 
     @Override
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/browser/UsernamePasswordFormFactory.java b/services/src/main/java/org/keycloak/authentication/authenticators/browser/UsernamePasswordFormFactory.java
index 392a02a..e9afe05 100755
--- a/services/src/main/java/org/keycloak/authentication/authenticators/browser/UsernamePasswordFormFactory.java
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/browser/UsernamePasswordFormFactory.java
@@ -18,15 +18,11 @@ import java.util.List;
 public class UsernamePasswordFormFactory implements AuthenticatorFactory {
 
     public static final String PROVIDER_ID = "auth-username-password-form";
-
-    @Override
-    public Authenticator create() {
-        return new UsernamePasswordForm();
-    }
+    public static final UsernamePasswordForm SINGLETON = new UsernamePasswordForm();
 
     @Override
     public Authenticator create(KeycloakSession session) {
-        throw new IllegalStateException("illegal call");
+        return SINGLETON;
     }
 
     @Override
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/directgrant/AbstractDirectGrantAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/directgrant/AbstractDirectGrantAuthenticator.java
index 2faa7bc..13754aa 100755
--- a/services/src/main/java/org/keycloak/authentication/authenticators/directgrant/AbstractDirectGrantAuthenticator.java
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/directgrant/AbstractDirectGrantAuthenticator.java
@@ -33,11 +33,6 @@ public abstract class AbstractDirectGrantAuthenticator implements Authenticator,
     }
 
     @Override
-    public Authenticator create() {
-        return this;
-    }
-
-    @Override
     public void close() {
 
     }
diff --git a/services/src/main/java/org/keycloak/authentication/DefaultAuthenticationFlow.java b/services/src/main/java/org/keycloak/authentication/DefaultAuthenticationFlow.java
index 1df2545..18f1703 100755
--- a/services/src/main/java/org/keycloak/authentication/DefaultAuthenticationFlow.java
+++ b/services/src/main/java/org/keycloak/authentication/DefaultAuthenticationFlow.java
@@ -54,7 +54,7 @@ public class DefaultAuthenticationFlow implements AuthenticationFlow {
                 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();
+                Authenticator authenticator = factory.create(processor.getSession());
                 AuthenticationProcessor.Result result = processor.createAuthenticatorContext(model, authenticator, executions);
                 authenticator.action(result);
                 Response response = processResult(result);
@@ -108,7 +108,7 @@ public class DefaultAuthenticationFlow implements AuthenticationFlow {
             if (factory == null) {
                 throw new AuthenticationFlowException("Could not find AuthenticatorFactory for: " + model.getAuthenticator(), AuthenticationFlowError.INTERNAL_ERROR);
             }
-            Authenticator authenticator = factory.create();
+            Authenticator authenticator = factory.create(processor.getSession());
             AuthenticationProcessor.logger.debugv("authenticator: {0}", factory.getId());
             UserModel authUser = processor.getClientSession().getAuthenticatedUser();
 
diff --git a/services/src/main/java/org/keycloak/authentication/FormAction.java b/services/src/main/java/org/keycloak/authentication/FormAction.java
index 3dcdc4e..72a1576 100755
--- a/services/src/main/java/org/keycloak/authentication/FormAction.java
+++ b/services/src/main/java/org/keycloak/authentication/FormAction.java
@@ -16,6 +16,14 @@ import org.keycloak.provider.Provider;
  */
 public interface FormAction extends Provider {
     /**
+     * When a FormAuthenticator is rendering the challenge page, even FormAction.buildPage() method will be called
+     * This gives the FormAction the opportunity to add additional attributes to the form to be displayed.
+     *
+     * @param context
+     * @param form
+     */
+    void buildPage(FormContext context, LoginFormsProvider form);
+    /**
      * This is the first phase of form processing.  Each FormAction.validate() method is called.  This gives the
      * FormAction a chance to validate and challenge if user input is invalid.
      *
@@ -53,13 +61,5 @@ public interface FormAction extends Provider {
      */
     void setRequiredActions(KeycloakSession session, RealmModel realm, UserModel user);
 
-    /**
-     * When a FormAuthenticator is rendering the challenge page, even FormAction.buildPage() method will be called
-     * This gives the FormAction the opportunity to add additional attributes to the form to be displayed.
-     *
-     * @param context
-     * @param form
-     */
-    void buildPage(FormContext context, LoginFormsProvider form);
 
 }
diff --git a/services/src/main/java/org/keycloak/authentication/forms/RegistrationRecaptcha.java b/services/src/main/java/org/keycloak/authentication/forms/RegistrationRecaptcha.java
index df75e4f..3c1817c 100755
--- a/services/src/main/java/org/keycloak/authentication/forms/RegistrationRecaptcha.java
+++ b/services/src/main/java/org/keycloak/authentication/forms/RegistrationRecaptcha.java
@@ -86,9 +86,7 @@ public class RegistrationRecaptcha implements FormAction, FormActionFactory, Con
         String siteKey = captchaConfig.getConfig().get(SITE_KEY);
         form.setAttribute("recaptchaRequired", true);
         form.setAttribute("recaptchaSiteKey", siteKey);
-        List<String> scripts = new LinkedList<>();
-        scripts.add("https://www.google.com/recaptcha/api.js");
-        form.setAttribute("scripts", scripts);
+        form.addScript("https://www.google.com/recaptcha/api.js");
     }
 
     @Override
@@ -103,27 +101,7 @@ public class RegistrationRecaptcha implements FormAction, FormActionFactory, Con
             AuthenticatorConfigModel captchaConfig = context.getAuthenticatorConfig();
             String secret = captchaConfig.getConfig().get(SITE_SECRET);
 
-            HttpClient httpClient = context.getSession().getProvider(HttpClientProvider.class).getHttpClient();
-            HttpPost post = new HttpPost("https://www.google.com/recaptcha/api/siteverify");
-            List<NameValuePair> formparams = new LinkedList<>();
-            formparams.add(new BasicNameValuePair("secret", secret));
-            formparams.add(new BasicNameValuePair("response", captcha));
-            formparams.add(new BasicNameValuePair("remoteip", context.getConnection().getRemoteAddr()));
-            try {
-                UrlEncodedFormEntity form = new UrlEncodedFormEntity(formparams, "UTF-8");
-                post.setEntity(form);
-                HttpResponse response = httpClient.execute(post);
-                InputStream content = response.getEntity().getContent();
-                try {
-                    Map json = JsonSerialization.readValue(content, Map.class);
-                    Object val = json.get("success");
-                    success = Boolean.TRUE.equals(val);
-                } finally {
-                    content.close();
-                }
-            } catch (Exception e) {
-                logger.error("Recaptcha failed", e);
-            }
+            success = validateRecaptcha(context, success, captcha, secret);
         }
         if (success) {
             context.success();
@@ -138,6 +116,31 @@ public class RegistrationRecaptcha implements FormAction, FormActionFactory, Con
         }
     }
 
+    protected boolean validateRecaptcha(ValidationContext context, boolean success, String captcha, String secret) {
+        HttpClient httpClient = context.getSession().getProvider(HttpClientProvider.class).getHttpClient();
+        HttpPost post = new HttpPost("https://www.google.com/recaptcha/api/siteverify");
+        List<NameValuePair> formparams = new LinkedList<>();
+        formparams.add(new BasicNameValuePair("secret", secret));
+        formparams.add(new BasicNameValuePair("response", captcha));
+        formparams.add(new BasicNameValuePair("remoteip", context.getConnection().getRemoteAddr()));
+        try {
+            UrlEncodedFormEntity form = new UrlEncodedFormEntity(formparams, "UTF-8");
+            post.setEntity(form);
+            HttpResponse response = httpClient.execute(post);
+            InputStream content = response.getEntity().getContent();
+            try {
+                Map json = JsonSerialization.readValue(content, Map.class);
+                Object val = json.get("success");
+                success = Boolean.TRUE.equals(val);
+            } finally {
+                content.close();
+            }
+        } catch (Exception e) {
+            logger.error("Recaptcha failed", e);
+        }
+        return success;
+    }
+
     @Override
     public void success(FormContext context) {
 
diff --git a/services/src/main/java/org/keycloak/authentication/RequiredActionContextResult.java b/services/src/main/java/org/keycloak/authentication/RequiredActionContextResult.java
index 279b25c..b9e06f8 100755
--- a/services/src/main/java/org/keycloak/authentication/RequiredActionContextResult.java
+++ b/services/src/main/java/org/keycloak/authentication/RequiredActionContextResult.java
@@ -31,12 +31,12 @@ public class RequiredActionContextResult implements RequiredActionContext {
     protected Response challenge;
     protected HttpRequest httpRequest;
     protected UserModel user;
-    protected RequiredActionProvider provider;
+    protected RequiredActionFactory factory;
 
     public RequiredActionContextResult(UserSessionModel userSession, ClientSessionModel clientSession,
                                        RealmModel realm, EventBuilder eventBuilder, KeycloakSession session,
                                        HttpRequest httpRequest,
-                                       UserModel user, RequiredActionProvider provider) {
+                                       UserModel user, RequiredActionFactory factory) {
         this.userSession = userSession;
         this.clientSession = clientSession;
         this.realm = realm;
@@ -44,7 +44,7 @@ public class RequiredActionContextResult implements RequiredActionContext {
         this.session = session;
         this.httpRequest = httpRequest;
         this.user = user;
-        this.provider = provider;
+        this.factory = factory;
     }
 
     @Override
@@ -131,20 +131,20 @@ public class RequiredActionContextResult implements RequiredActionContext {
     public URI getActionUrl(String code) {
         return LoginActionsService.requiredActionProcessor(getUriInfo())
                 .queryParam(OAuth2Constants.CODE, code)
-                .queryParam("action", provider.getProviderId())
+                .queryParam("action", factory.getId())
                 .build(getRealm().getName());
     }
 
     @Override
     public URI getActionUrl() {
-        String accessCode = generateAccessCode(provider.getProviderId());
+        String accessCode = generateAccessCode(factory.getId());
         return getActionUrl(accessCode);
 
     }
 
     @Override
     public LoginFormsProvider form() {
-        String accessCode = generateAccessCode(provider.getProviderId());
+        String accessCode = generateAccessCode(factory.getId());
         URI action = getActionUrl(accessCode);
         LoginFormsProvider provider = getSession().getProvider(LoginFormsProvider.class)
                 .setUser(getUser())
diff --git a/services/src/main/java/org/keycloak/authentication/RequiredActionProvider.java b/services/src/main/java/org/keycloak/authentication/RequiredActionProvider.java
index f6b1e62..94a9f2a 100755
--- a/services/src/main/java/org/keycloak/authentication/RequiredActionProvider.java
+++ b/services/src/main/java/org/keycloak/authentication/RequiredActionProvider.java
@@ -36,11 +36,4 @@ public interface RequiredActionProvider extends Provider {
      * @param context
      */
     void processAction(RequiredActionContext context);
-
-    /**
-     * Provider id of this required action.  Must match ProviderFactory.getId().
-     *
-     * @return
-     */
-    String getProviderId();
 }
diff --git a/services/src/main/java/org/keycloak/authentication/requiredactions/TermsAndConditions.java b/services/src/main/java/org/keycloak/authentication/requiredactions/TermsAndConditions.java
index e3ecc9d..cb5ecd2 100755
--- a/services/src/main/java/org/keycloak/authentication/requiredactions/TermsAndConditions.java
+++ b/services/src/main/java/org/keycloak/authentication/requiredactions/TermsAndConditions.java
@@ -39,13 +39,6 @@ public class TermsAndConditions implements RequiredActionProvider, RequiredActio
 
 
     @Override
-    public String getProviderId() {
-        return getId();
-    }
-
-
-
-    @Override
     public void evaluateTriggers(RequiredActionContext context) {
 
     }
diff --git a/services/src/main/java/org/keycloak/authentication/requiredactions/UpdatePassword.java b/services/src/main/java/org/keycloak/authentication/requiredactions/UpdatePassword.java
index 4ef413d..f88cdc8 100755
--- a/services/src/main/java/org/keycloak/authentication/requiredactions/UpdatePassword.java
+++ b/services/src/main/java/org/keycloak/authentication/requiredactions/UpdatePassword.java
@@ -51,7 +51,7 @@ public class UpdatePassword implements RequiredActionProvider, RequiredActionFac
     public void requiredActionChallenge(RequiredActionContext context) {
         LoginFormsProvider loginFormsProvider = context.getSession()
                 .getProvider(LoginFormsProvider.class)
-                .setClientSessionCode(context.generateAccessCode(getProviderId()))
+                .setClientSessionCode(context.generateAccessCode(UserModel.RequiredAction.UPDATE_PASSWORD.name()))
                 .setUser(context.getUser());
         Response challenge = loginFormsProvider.createResponse(UserModel.RequiredAction.UPDATE_PASSWORD);
         context.challenge(challenge);
@@ -92,10 +92,4 @@ public class UpdatePassword implements RequiredActionProvider, RequiredActionFac
     public String getId() {
         return UserModel.RequiredAction.UPDATE_PASSWORD.name();
     }
-
-    @Override
-    public String getProviderId() {
-        return getId();
-    }
-
 }
diff --git a/services/src/main/java/org/keycloak/authentication/requiredactions/UpdateProfile.java b/services/src/main/java/org/keycloak/authentication/requiredactions/UpdateProfile.java
index 4cb8493..9ca78a6 100755
--- a/services/src/main/java/org/keycloak/authentication/requiredactions/UpdateProfile.java
+++ b/services/src/main/java/org/keycloak/authentication/requiredactions/UpdateProfile.java
@@ -25,7 +25,7 @@ public class UpdateProfile implements RequiredActionProvider, RequiredActionFact
     @Override
     public void requiredActionChallenge(RequiredActionContext context) {
         LoginFormsProvider loginFormsProvider = context.getSession().getProvider(LoginFormsProvider.class)
-                .setClientSessionCode(context.generateAccessCode(getProviderId()))
+                .setClientSessionCode(context.generateAccessCode(UserModel.RequiredAction.UPDATE_PROFILE.name()))
                 .setUser(context.getUser());
         Response challenge = loginFormsProvider.createResponse(UserModel.RequiredAction.UPDATE_PROFILE);
         context.challenge(challenge);
@@ -67,10 +67,4 @@ public class UpdateProfile implements RequiredActionProvider, RequiredActionFact
     public String getId() {
         return UserModel.RequiredAction.UPDATE_PROFILE.name();
     }
-
-    @Override
-    public String getProviderId() {
-        return getId();
-    }
-
 }
diff --git a/services/src/main/java/org/keycloak/authentication/requiredactions/UpdateTotp.java b/services/src/main/java/org/keycloak/authentication/requiredactions/UpdateTotp.java
index c958357..61b035a 100755
--- a/services/src/main/java/org/keycloak/authentication/requiredactions/UpdateTotp.java
+++ b/services/src/main/java/org/keycloak/authentication/requiredactions/UpdateTotp.java
@@ -27,7 +27,7 @@ public class UpdateTotp implements RequiredActionProvider, RequiredActionFactory
     @Override
     public void requiredActionChallenge(RequiredActionContext context) {
          LoginFormsProvider loginFormsProvider = context.getSession().getProvider(LoginFormsProvider.class)
-                 .setClientSessionCode(context.generateAccessCode(getProviderId()))
+                 .setClientSessionCode(context.generateAccessCode(UserModel.RequiredAction.CONFIGURE_TOTP.name()))
                 .setUser(context.getUser());
         Response challenge = loginFormsProvider.createResponse(UserModel.RequiredAction.CONFIGURE_TOTP);
         context.challenge(challenge);
@@ -69,11 +69,4 @@ public class UpdateTotp implements RequiredActionProvider, RequiredActionFactory
     public String getId() {
         return UserModel.RequiredAction.CONFIGURE_TOTP.name();
     }
-
-    @Override
-    public String getProviderId() {
-        return getId();
-    }
-
-
 }
diff --git a/services/src/main/java/org/keycloak/authentication/requiredactions/VerifyEmail.java b/services/src/main/java/org/keycloak/authentication/requiredactions/VerifyEmail.java
index ba0ee06..f2096cc 100755
--- a/services/src/main/java/org/keycloak/authentication/requiredactions/VerifyEmail.java
+++ b/services/src/main/java/org/keycloak/authentication/requiredactions/VerifyEmail.java
@@ -44,7 +44,7 @@ public class VerifyEmail implements RequiredActionProvider, RequiredActionFactor
         LoginActionsService.createActionCookie(context.getRealm(), context.getUriInfo(), context.getConnection(), context.getUserSession().getId());
 
         LoginFormsProvider loginFormsProvider = context.getSession().getProvider(LoginFormsProvider.class)
-                .setClientSessionCode(context.generateAccessCode(getProviderId()))
+                .setClientSessionCode(context.generateAccessCode(UserModel.RequiredAction.VERIFY_EMAIL.name()))
                 .setUser(context.getUser());
         Response challenge = loginFormsProvider.createResponse(UserModel.RequiredAction.VERIFY_EMAIL);
         context.challenge(challenge);
@@ -86,11 +86,4 @@ public class VerifyEmail implements RequiredActionProvider, RequiredActionFactor
     public String getId() {
         return UserModel.RequiredAction.VERIFY_EMAIL.name();
     }
-
-    @Override
-    public String getProviderId() {
-        return getId();
-    }
-
-
 }
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 bd50ff0..82e0d4d 100755
--- a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
@@ -8,6 +8,7 @@ import org.keycloak.RSATokenVerifier;
 import org.keycloak.VerificationException;
 import org.keycloak.authentication.RequiredActionContext;
 import org.keycloak.authentication.RequiredActionContextResult;
+import org.keycloak.authentication.RequiredActionFactory;
 import org.keycloak.authentication.RequiredActionProvider;
 import org.keycloak.broker.provider.IdentityProvider;
 import org.keycloak.events.Details;
@@ -431,8 +432,9 @@ public class AuthenticationManager {
         Set<String> requiredActions = user.getRequiredActions();
         for (String action : requiredActions) {
             RequiredActionProviderModel model = realm.getRequiredActionProviderByAlias(action);
-            RequiredActionProvider actionProvider = session.getProvider(RequiredActionProvider.class, model.getProviderId());
-            RequiredActionContextResult context = new RequiredActionContextResult(userSession, clientSession, realm, event, session, request, user, actionProvider);
+            RequiredActionFactory factory = (RequiredActionFactory)session.getKeycloakSessionFactory().getProviderFactory(RequiredActionProvider.class, model.getProviderId());
+            RequiredActionProvider actionProvider = factory.create(session);
+            RequiredActionContextResult context = new RequiredActionContextResult(userSession, clientSession, realm, event, session, request, user, factory);
             actionProvider.requiredActionChallenge(context);
 
             if (context.getStatus() == RequiredActionContext.Status.FAILURE) {
@@ -447,8 +449,8 @@ public class AuthenticationManager {
                 return context.getChallenge();
             }
             else if (context.getStatus() == RequiredActionContext.Status.SUCCESS) {
-                event.clone().event(EventType.CUSTOM_REQUIRED_ACTION).detail(Details.CUSTOM_REQUIRED_ACTION, actionProvider.getProviderId()).success();
-                clientSession.getUserSession().getUser().removeRequiredAction(actionProvider.getProviderId());
+                event.clone().event(EventType.CUSTOM_REQUIRED_ACTION).detail(Details.CUSTOM_REQUIRED_ACTION, factory.getId()).success();
+                clientSession.getUserSession().getUser().removeRequiredAction(factory.getId());
             }
         }
         if (client.isConsentRequired()) {
@@ -505,8 +507,9 @@ public class AuthenticationManager {
         // see if any required actions need triggering, i.e. an expired password
         for (RequiredActionProviderModel model : realm.getRequiredActionProviders()) {
             if (!model.isEnabled()) continue;
-            RequiredActionProvider provider = session.getProvider(RequiredActionProvider.class, model.getProviderId());
-            RequiredActionContextResult result = new RequiredActionContextResult(userSession, clientSession, realm, event, session, request, user, provider) {
+            RequiredActionFactory factory = (RequiredActionFactory)session.getKeycloakSessionFactory().getProviderFactory(RequiredActionProvider.class, model.getProviderId());
+            RequiredActionProvider provider = factory.create(session);
+            RequiredActionContextResult result = new RequiredActionContextResult(userSession, clientSession, realm, event, session, request, user, factory) {
                 @Override
                 public void challenge(Response response) {
                     throw new RuntimeException("Not allowed to call challenge() within evaluateTriggers()");
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 a907237..e65d0de 100755
--- a/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java
+++ b/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java
@@ -27,6 +27,7 @@ import org.keycloak.ClientConnection;
 import org.keycloak.authentication.AuthenticationProcessor;
 import org.keycloak.authentication.RequiredActionContext;
 import org.keycloak.authentication.RequiredActionContextResult;
+import org.keycloak.authentication.RequiredActionFactory;
 import org.keycloak.authentication.RequiredActionProvider;
 import org.keycloak.email.EmailException;
 import org.keycloak.email.EmailProvider;
@@ -861,12 +862,13 @@ public class LoginActionsService {
 
         }
 
-        RequiredActionProvider provider = session.getProvider(RequiredActionProvider.class, action);
-        if (provider == null) {
+        RequiredActionFactory factory = (RequiredActionFactory)session.getKeycloakSessionFactory().getProviderFactory(RequiredActionProvider.class, action);
+        if (factory == null) {
             logger.error("required action provider was null");
             event.error(Errors.INVALID_CODE);
             throw new WebApplicationException(ErrorPage.error(session, Messages.INVALID_CODE));
         }
+        RequiredActionProvider provider = factory.create(session);
         Checks checks = new Checks();
         if (!checks.verifyCode(realm.getBrowserFlow(), code, action)) {
             return checks.response;
@@ -883,7 +885,7 @@ public class LoginActionsService {
         initEvent(clientSession);
 
 
-        RequiredActionContextResult context = new RequiredActionContextResult(clientSession.getUserSession(), clientSession, realm, event, session, request, clientSession.getUserSession().getUser(), provider) {
+        RequiredActionContextResult context = new RequiredActionContextResult(clientSession.getUserSession(), clientSession, realm, event, session, request, clientSession.getUserSession().getUser(), factory) {
              @Override
             public String generateAccessCode(String action) {
                 String clientSessionAction = clientSession.getAction();
@@ -905,7 +907,7 @@ public class LoginActionsService {
         if (context.getStatus() == RequiredActionContext.Status.SUCCESS) {
             event.clone().event(EventType.CUSTOM_REQUIRED_ACTION)
                          .detail(Details.CUSTOM_REQUIRED_ACTION, action).success();
-            clientSession.getUserSession().getUser().removeRequiredAction(provider.getProviderId());
+            clientSession.getUserSession().getUser().removeRequiredAction(factory.getId());
             return AuthenticationManager.nextActionAfterAuthentication(session, clientSession.getUserSession(), clientSession, clientConnection, request, uriInfo, event);
         }
         if (context.getStatus() == RequiredActionContext.Status.CHALLENGE) {
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/PassThroughAuthenticator.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/PassThroughAuthenticator.java
index 1b83d29..f252e2f 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/PassThroughAuthenticator.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/PassThroughAuthenticator.java
@@ -54,12 +54,7 @@ public class PassThroughAuthenticator implements Authenticator, AuthenticatorFac
 
     }
 
-    @Override
-    public Authenticator create() {
-        return this;
-    }
-
-    @Override
+   @Override
     public String getDisplayType() {
         return "Dummy Pass Thru";
     }
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/PassThroughRegistration.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/PassThroughRegistration.java
index 0616ee6..35aa312 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/PassThroughRegistration.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/PassThroughRegistration.java
@@ -71,11 +71,6 @@ public class PassThroughRegistration implements Authenticator, AuthenticatorFact
     }
 
     @Override
-    public Authenticator create() {
-        return this;
-    }
-
-    @Override
     public String getDisplayType() {
         return "Dummy Pass Thru";
     }