diff --git a/examples/providers/authenticator/README.md b/examples/providers/authenticator/README.md
new file mode 100755
index 0000000000..ef612cc2a7
--- /dev/null
+++ b/examples/providers/authenticator/README.md
@@ -0,0 +1,23 @@
+Example Custom Authenticator
+===================================================
+
+1. First, Keycloak must be running.
+
+2. Execute the follow. This will build the example and deploy it
+
+ $ mvn clean install wildfly:deploy
+
+3. Copy the secret-question.ftl and secret-question-config.ftl files to the themes/base/login directory.
+
+4. Login to admin console. Hit browser refresh if you are already logged in so that the new providers show up.
+
+5. 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 you
+ have to copy an existing flow or create your own. Copy the "Browser" flow.
+
+6. In your copy, click the "Actions" menu item and "Add Execution". Pick Secret Question
+
+7. Next you have to register the required action that you created. Click on the Required Actions tab in the Authenticaiton menu.
+ Click on the Register button and choose your new Required Action.
+ Your new required action should now be displayed and enabled in the required actions list.
+
diff --git a/examples/providers/authenticator/pom.xml b/examples/providers/authenticator/pom.xml
new file mode 100755
index 0000000000..a89c7f8bf6
--- /dev/null
+++ b/examples/providers/authenticator/pom.xml
@@ -0,0 +1,73 @@
+
+
+
+
+ keycloak-examples-providers-parent
+ org.keycloak
+ 5.0.0-SNAPSHOT
+
+
+ Authenticator Example
+
+ 4.0.0
+
+ authenticator-required-action-example
+ jar
+
+
+
+ org.keycloak
+ keycloak-core
+ provided
+
+
+ org.keycloak
+ keycloak-server-spi
+ provided
+
+
+ org.keycloak
+ keycloak-server-spi-private
+ provided
+
+
+ org.jboss.logging
+ jboss-logging
+ provided
+
+
+ org.keycloak
+ keycloak-services
+ provided
+
+
+
+
+ authenticator-required-action-example
+
+
+ org.wildfly.plugins
+ wildfly-maven-plugin
+
+ false
+
+
+
+
+
diff --git a/examples/providers/authenticator/secret-question-config.ftl b/examples/providers/authenticator/secret-question-config.ftl
new file mode 100755
index 0000000000..54e69026b0
--- /dev/null
+++ b/examples/providers/authenticator/secret-question-config.ftl
@@ -0,0 +1,33 @@
+<#import "template.ftl" as layout>
+<@layout.registrationLayout; section>
+ <#if section = "title">
+ ${msg("loginTitle",realm.name)}
+ <#elseif section = "header">
+ Setup Secret Question
+ <#elseif section = "form">
+
+ #if>
+@layout.registrationLayout>
\ No newline at end of file
diff --git a/examples/providers/authenticator/secret-question.ftl b/examples/providers/authenticator/secret-question.ftl
new file mode 100755
index 0000000000..b69c5206a2
--- /dev/null
+++ b/examples/providers/authenticator/secret-question.ftl
@@ -0,0 +1,34 @@
+<#import "template.ftl" as layout>
+<@layout.registrationLayout; section>
+ <#if section = "title">
+ ${msg("loginTitle",realm.name)}
+ <#elseif section = "header">
+ ${msg("loginTitleHtml",realm.name)}
+ <#elseif section = "form">
+
+ #if>
+@layout.registrationLayout>
\ No newline at end of file
diff --git a/examples/providers/authenticator/src/main/java/org/keycloak/examples/authenticator/SecretQuestionAuthenticator.java b/examples/providers/authenticator/src/main/java/org/keycloak/examples/authenticator/SecretQuestionAuthenticator.java
new file mode 100755
index 0000000000..73d821fbcd
--- /dev/null
+++ b/examples/providers/authenticator/src/main/java/org/keycloak/examples/authenticator/SecretQuestionAuthenticator.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.examples.authenticator;
+
+import org.jboss.resteasy.spi.HttpResponse;
+import org.jboss.resteasy.spi.ResteasyProviderFactory;
+import org.keycloak.authentication.AuthenticationFlowContext;
+import org.keycloak.authentication.AuthenticationFlowError;
+import org.keycloak.authentication.Authenticator;
+import org.keycloak.common.util.ServerCookie;
+import org.keycloak.models.AuthenticatorConfigModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserCredentialModel;
+import org.keycloak.models.UserModel;
+
+import javax.ws.rs.core.Cookie;
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.core.Response;
+import java.net.URI;
+
+/**
+ * @author Bill Burke
+ * @version $Revision: 1 $
+ */
+public class SecretQuestionAuthenticator implements Authenticator {
+
+ public static final String CREDENTIAL_TYPE = "secret_question";
+
+ protected boolean hasCookie(AuthenticationFlowContext context) {
+ Cookie cookie = context.getHttpRequest().getHttpHeaders().getCookies().get("SECRET_QUESTION_ANSWERED");
+ boolean result = cookie != null;
+ if (result) {
+ System.out.println("Bypassing secret question because cookie is set");
+ }
+ return result;
+ }
+
+ @Override
+ public void authenticate(AuthenticationFlowContext context) {
+ if (hasCookie(context)) {
+ context.success();
+ return;
+ }
+ Response challenge = context.form().createForm("secret-question.ftl");
+ context.challenge(challenge);
+ }
+
+ @Override
+ public void action(AuthenticationFlowContext context) {
+ MultivaluedMap formData = context.getHttpRequest().getDecodedFormParameters();
+ if (formData.containsKey("cancel")) {
+ context.cancelLogin();
+ return;
+ }
+ boolean validated = validateAnswer(context);
+ if (!validated) {
+ Response challenge = context.form()
+ .setError("badSecret")
+ .createForm("secret-question.ftl");
+ context.failureChallenge(AuthenticationFlowError.INVALID_CREDENTIALS, challenge);
+ return;
+ }
+ setCookie(context);
+ context.success();
+ }
+
+ protected void setCookie(AuthenticationFlowContext context) {
+ AuthenticatorConfigModel config = context.getAuthenticatorConfig();
+ int maxCookieAge = 60 * 60 * 24 * 30; // 30 days
+ if (config != null) {
+ maxCookieAge = Integer.valueOf(config.getConfig().get("cookie.max.age"));
+
+ }
+ URI uri = context.getUriInfo().getBaseUriBuilder().path("realms").path(context.getRealm().getName()).build();
+ addCookie("SECRET_QUESTION_ANSWERED", "true",
+ uri.getRawPath(),
+ null, null,
+ maxCookieAge,
+ false, true);
+ }
+
+ public static void addCookie(String name, String value, String path, String domain, String comment, int maxAge, boolean secure, boolean httpOnly) {
+ HttpResponse response = ResteasyProviderFactory.getContextData(HttpResponse.class);
+ StringBuffer cookieBuf = new StringBuffer();
+ ServerCookie.appendCookieValue(cookieBuf, 1, name, value, path, domain, comment, maxAge, secure, httpOnly);
+ String cookie = cookieBuf.toString();
+ response.getOutputHeaders().add(HttpHeaders.SET_COOKIE, cookie);
+ }
+
+
+ protected boolean validateAnswer(AuthenticationFlowContext context) {
+ MultivaluedMap formData = context.getHttpRequest().getDecodedFormParameters();
+ String secret = formData.getFirst("secret_answer");
+ UserCredentialModel input = new UserCredentialModel();
+ input.setType(SecretQuestionCredentialProvider.SECRET_QUESTION);
+ input.setValue(secret);
+ return context.getSession().userCredentialManager().isValid(context.getRealm(), context.getUser(), input);
+ }
+
+ @Override
+ public boolean requiresUser() {
+ return true;
+ }
+
+ @Override
+ public boolean configuredFor(KeycloakSession session, RealmModel realm, UserModel user) {
+ return session.userCredentialManager().isConfiguredFor(realm, user, SecretQuestionCredentialProvider.SECRET_QUESTION);
+ }
+
+ @Override
+ public void setRequiredActions(KeycloakSession session, RealmModel realm, UserModel user) {
+ user.addRequiredAction(SecretQuestionRequiredAction.PROVIDER_ID);
+ }
+
+ @Override
+ public void close() {
+
+ }
+}
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
new file mode 100755
index 0000000000..77c93b6e23
--- /dev/null
+++ b/examples/providers/authenticator/src/main/java/org/keycloak/examples/authenticator/SecretQuestionAuthenticatorFactory.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.examples.authenticator;
+
+import org.keycloak.Config;
+import org.keycloak.authentication.Authenticator;
+import org.keycloak.authentication.AuthenticatorFactory;
+import org.keycloak.authentication.ConfigurableAuthenticatorFactory;
+import org.keycloak.models.AuthenticationExecutionModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakSessionFactory;
+import org.keycloak.provider.ProviderConfigProperty;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author Bill Burke
+ * @version $Revision: 1 $
+ */
+public class SecretQuestionAuthenticatorFactory implements AuthenticatorFactory, ConfigurableAuthenticatorFactory {
+
+ public static final String PROVIDER_ID = "secret-question-authenticator";
+ private static final SecretQuestionAuthenticator SINGLETON = new SecretQuestionAuthenticator();
+
+ @Override
+ public String getId() {
+ return PROVIDER_ID;
+ }
+
+ @Override
+ public Authenticator create(KeycloakSession session) {
+ return SINGLETON;
+ }
+
+ private static AuthenticationExecutionModel.Requirement[] REQUIREMENT_CHOICES = {
+ AuthenticationExecutionModel.Requirement.REQUIRED,
+ AuthenticationExecutionModel.Requirement.DISABLED
+ };
+ @Override
+ public AuthenticationExecutionModel.Requirement[] getRequirementChoices() {
+ return REQUIREMENT_CHOICES;
+ }
+
+ @Override
+ public boolean isUserSetupAllowed() {
+ return true;
+ }
+
+ @Override
+ public boolean isConfigurable() {
+ return true;
+ }
+
+ @Override
+ public List getConfigProperties() {
+ return configProperties;
+ }
+
+ private static final List configProperties = new ArrayList();
+
+ static {
+ ProviderConfigProperty property;
+ property = new ProviderConfigProperty();
+ property.setName("cookie.max.age");
+ property.setLabel("Cookie Max Age");
+ property.setType(ProviderConfigProperty.STRING_TYPE);
+ property.setHelpText("Max age in seconds of the SECRET_QUESTION_COOKIE.");
+ configProperties.add(property);
+ }
+
+
+ @Override
+ public String getHelpText() {
+ return "A secret question that a user has to answer. i.e. What is your mother's maiden name.";
+ }
+
+ @Override
+ public String getDisplayType() {
+ return "Secret Question";
+ }
+
+ @Override
+ public String getReferenceCategory() {
+ return "Secret Question";
+ }
+
+ @Override
+ public void init(Config.Scope config) {
+
+ }
+
+ @Override
+ public void postInit(KeycloakSessionFactory factory) {
+
+ }
+
+ @Override
+ public void close() {
+
+ }
+
+
+}
diff --git a/examples/providers/authenticator/src/main/java/org/keycloak/examples/authenticator/SecretQuestionCredentialProvider.java b/examples/providers/authenticator/src/main/java/org/keycloak/examples/authenticator/SecretQuestionCredentialProvider.java
new file mode 100644
index 0000000000..76b3239ea9
--- /dev/null
+++ b/examples/providers/authenticator/src/main/java/org/keycloak/examples/authenticator/SecretQuestionCredentialProvider.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.keycloak.examples.authenticator;
+
+import org.keycloak.common.util.Time;
+import org.keycloak.credential.CredentialInput;
+import org.keycloak.credential.CredentialInputUpdater;
+import org.keycloak.credential.CredentialInputValidator;
+import org.keycloak.credential.CredentialModel;
+import org.keycloak.credential.CredentialProvider;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserCredentialModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.models.cache.CachedUserModel;
+import org.keycloak.models.cache.OnUserCache;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * @author Bill Burke
+ * @version $Revision: 1 $
+ */
+public class SecretQuestionCredentialProvider implements CredentialProvider, CredentialInputValidator, CredentialInputUpdater, OnUserCache {
+ public static final String SECRET_QUESTION = "SECRET_QUESTION";
+ public static final String CACHE_KEY = SecretQuestionCredentialProvider.class.getName() + "." + SECRET_QUESTION;
+
+ protected KeycloakSession session;
+
+ public SecretQuestionCredentialProvider(KeycloakSession session) {
+ this.session = session;
+ }
+
+ public CredentialModel getSecret(RealmModel realm, UserModel user) {
+ CredentialModel secret = null;
+ if (user instanceof CachedUserModel) {
+ CachedUserModel cached = (CachedUserModel)user;
+ secret = (CredentialModel)cached.getCachedWith().get(CACHE_KEY);
+
+ } else {
+ List creds = session.userCredentialManager().getStoredCredentialsByType(realm, user, SECRET_QUESTION);
+ if (!creds.isEmpty()) secret = creds.get(0);
+ }
+ return secret;
+ }
+
+
+ @Override
+ public boolean updateCredential(RealmModel realm, UserModel user, CredentialInput input) {
+ if (!SECRET_QUESTION.equals(input.getType())) return false;
+ if (!(input instanceof UserCredentialModel)) return false;
+ UserCredentialModel credInput = (UserCredentialModel) input;
+ List creds = session.userCredentialManager().getStoredCredentialsByType(realm, user, SECRET_QUESTION);
+ if (creds.isEmpty()) {
+ CredentialModel secret = new CredentialModel();
+ secret.setType(SECRET_QUESTION);
+ secret.setValue(credInput.getValue());
+ secret.setCreatedDate(Time.currentTimeMillis());
+ session.userCredentialManager().createCredential(realm ,user, secret);
+ } else {
+ creds.get(0).setValue(credInput.getValue());
+ session.userCredentialManager().updateCredential(realm, user, creds.get(0));
+ }
+ session.userCache().evict(realm, user);
+ return true;
+ }
+
+ @Override
+ public void disableCredentialType(RealmModel realm, UserModel user, String credentialType) {
+ if (!SECRET_QUESTION.equals(credentialType)) return;
+
+ List credentials = session.userCredentialManager().getStoredCredentialsByType(realm, user, SECRET_QUESTION);
+ for (CredentialModel cred : credentials) {
+ session.userCredentialManager().removeStoredCredential(realm, user, cred.getId());
+ }
+ session.userCache().evict(realm, user);
+ }
+
+ @Override
+ public Set getDisableableCredentialTypes(RealmModel realm, UserModel user) {
+ if (!session.userCredentialManager().getStoredCredentialsByType(realm, user, SECRET_QUESTION).isEmpty()) {
+ Set set = new HashSet<>();
+ set.add(SECRET_QUESTION);
+ return set;
+ } else {
+ return Collections.EMPTY_SET;
+ }
+
+ }
+
+ @Override
+ public boolean supportsCredentialType(String credentialType) {
+ return SECRET_QUESTION.equals(credentialType);
+ }
+
+ @Override
+ public boolean isConfiguredFor(RealmModel realm, UserModel user, String credentialType) {
+ if (!SECRET_QUESTION.equals(credentialType)) return false;
+ return getSecret(realm, user) != null;
+ }
+
+ @Override
+ public boolean isValid(RealmModel realm, UserModel user, CredentialInput input) {
+ if (!SECRET_QUESTION.equals(input.getType())) return false;
+ if (!(input instanceof UserCredentialModel)) return false;
+
+ String secret = getSecret(realm, user).getValue();
+
+ return secret != null && ((UserCredentialModel)input).getValue().equals(secret);
+ }
+
+ @Override
+ public void onCache(RealmModel realm, CachedUserModel user, UserModel delegate) {
+ List creds = session.userCredentialManager().getStoredCredentialsByType(realm, user, SECRET_QUESTION);
+ if (!creds.isEmpty()) user.getCachedWith().put(CACHE_KEY, creds.get(0));
+ }
+}
diff --git a/examples/providers/authenticator/src/main/java/org/keycloak/examples/authenticator/SecretQuestionCredentialProviderFactory.java b/examples/providers/authenticator/src/main/java/org/keycloak/examples/authenticator/SecretQuestionCredentialProviderFactory.java
new file mode 100644
index 0000000000..98b65ae9df
--- /dev/null
+++ b/examples/providers/authenticator/src/main/java/org/keycloak/examples/authenticator/SecretQuestionCredentialProviderFactory.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.keycloak.examples.authenticator;
+
+import org.keycloak.credential.CredentialProvider;
+import org.keycloak.credential.CredentialProviderFactory;
+import org.keycloak.models.KeycloakSession;
+
+/**
+ * @author Bill Burke
+ * @version $Revision: 1 $
+ */
+public class SecretQuestionCredentialProviderFactory implements CredentialProviderFactory {
+ @Override
+ public String getId() {
+ return "secret-question";
+ }
+
+ @Override
+ public CredentialProvider create(KeycloakSession session) {
+ return new SecretQuestionCredentialProvider(session);
+ }
+}
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
new file mode 100755
index 0000000000..cc1425e742
--- /dev/null
+++ b/examples/providers/authenticator/src/main/java/org/keycloak/examples/authenticator/SecretQuestionRequiredAction.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.examples.authenticator;
+
+import org.keycloak.authentication.RequiredActionContext;
+import org.keycloak.authentication.RequiredActionProvider;
+import org.keycloak.models.UserCredentialModel;
+
+import javax.ws.rs.core.Response;
+
+/**
+ * @author Bill Burke
+ * @version $Revision: 1 $
+ */
+public class SecretQuestionRequiredAction implements RequiredActionProvider {
+ public static final String PROVIDER_ID = "secret_question_config";
+
+ @Override
+ public void evaluateTriggers(RequiredActionContext context) {
+
+ }
+
+ @Override
+ public void requiredActionChallenge(RequiredActionContext context) {
+ Response challenge = context.form().createForm("secret-question-config.ftl");
+ context.challenge(challenge);
+
+ }
+
+ @Override
+ public void processAction(RequiredActionContext context) {
+ String answer = (context.getHttpRequest().getDecodedFormParameters().getFirst("secret_answer"));
+ UserCredentialModel input = new UserCredentialModel();
+ input.setType(SecretQuestionCredentialProvider.SECRET_QUESTION);
+ input.setValue(answer);
+ context.getSession().userCredentialManager().updateCredential(context.getRealm(), context.getUser(), input);
+ context.success();
+ }
+
+ @Override
+ public void close() {
+
+ }
+}
diff --git a/examples/providers/authenticator/src/main/java/org/keycloak/examples/authenticator/SecretQuestionRequiredActionFactory.java b/examples/providers/authenticator/src/main/java/org/keycloak/examples/authenticator/SecretQuestionRequiredActionFactory.java
new file mode 100755
index 0000000000..46ad4ebc75
--- /dev/null
+++ b/examples/providers/authenticator/src/main/java/org/keycloak/examples/authenticator/SecretQuestionRequiredActionFactory.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.examples.authenticator;
+
+import org.keycloak.Config;
+import org.keycloak.authentication.RequiredActionFactory;
+import org.keycloak.authentication.RequiredActionProvider;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakSessionFactory;
+
+/**
+ * @author Bill Burke
+ * @version $Revision: 1 $
+ */
+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";
+ }
+
+ @Override
+ public void init(Config.Scope config) {
+
+ }
+
+ @Override
+ public void postInit(KeycloakSessionFactory factory) {
+
+ }
+
+ @Override
+ public void close() {
+
+ }
+
+}
diff --git a/examples/providers/authenticator/src/main/resources/META-INF/services/org.keycloak.authentication.AuthenticatorFactory b/examples/providers/authenticator/src/main/resources/META-INF/services/org.keycloak.authentication.AuthenticatorFactory
new file mode 100755
index 0000000000..f288d3daa9
--- /dev/null
+++ b/examples/providers/authenticator/src/main/resources/META-INF/services/org.keycloak.authentication.AuthenticatorFactory
@@ -0,0 +1,18 @@
+#
+# Copyright 2016 Red Hat, Inc. and/or its affiliates
+# and other contributors as indicated by the @author tags.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+org.keycloak.examples.authenticator.SecretQuestionAuthenticatorFactory
\ No newline at end of file
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 0000000000..034c2f17c2
--- /dev/null
+++ b/examples/providers/authenticator/src/main/resources/META-INF/services/org.keycloak.authentication.RequiredActionFactory
@@ -0,0 +1,18 @@
+#
+# Copyright 2016 Red Hat, Inc. and/or its affiliates
+# and other contributors as indicated by the @author tags.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+org.keycloak.examples.authenticator.SecretQuestionRequiredActionFactory
\ No newline at end of file
diff --git a/examples/providers/authenticator/src/main/resources/META-INF/services/org.keycloak.credential.CredentialProviderFactory b/examples/providers/authenticator/src/main/resources/META-INF/services/org.keycloak.credential.CredentialProviderFactory
new file mode 100644
index 0000000000..a221e371cc
--- /dev/null
+++ b/examples/providers/authenticator/src/main/resources/META-INF/services/org.keycloak.credential.CredentialProviderFactory
@@ -0,0 +1 @@
+ org.keycloak.examples.authenticator.SecretQuestionCredentialProviderFactory
\ No newline at end of file
diff --git a/examples/providers/pom.xml b/examples/providers/pom.xml
index dc9734c907..d567966db8 100755
--- a/examples/providers/pom.xml
+++ b/examples/providers/pom.xml
@@ -33,5 +33,6 @@
rest
domain-extension
+ authenticator