Keycloak comes out of the box with a bunch of different authentication mechanisms: kerberos, password, and otp.
These mechanisms may not meet all of your requirements and you may want to plug in your own custom ones.
Keycloak provides an authentication SPI that you can use to write new plugins.
The admin console supports applying, ordering, and configuring these new mechanisms.
Keycloak also supports a simple registration form.
Different aspects of this form can be enabled and disabled i.e.
Recaptcha support can be turned off and on.
The same authentication SPI can be used to add another page to the registration flow or reimplement it entirely.
There's also an additional fine-grain SPI you can use to add specific validations and user extensions to the built in registration form.
A required action in Keycloak is an action that a user has to perform after he authenticates.
After the action is performed successfully, the user doesn't have to perform the action again.
Keycloak comes with some built in required actions like "reset password". This action forces the user to change their password after they have logged in.
You can write and plug in your own required actions.
The Cookie provider returns a success() status if the SSO cookie exists and is validated.
Since the cookie provider returned success and each execution at this level of the flow is ALTERNATIVE, no other execution is executed and this results in a successful login.
If there is no SSO cookie, the cookie provider returns with a status of attempted(). This means there was no error condition, but no success either.
The provider tried, but the request just wasn't set up to handle this authenticator.
The kerberos provider also does not require a user to be already set up and associated with the AuthenticationSessionModel so this provider is executed.
If the browser then responds with a successful negotiate header, the provider associates the user with the AuthenticationSession and the flow ends because the rest of the executions on this level of the flow are all alternatives.
Otherwise, again, the kerberos provider sets an attempted() status and the flow continues.
. The next execution is a subflow called Forms.
The executions for this subflow are loaded and the same processing logic occurs
. The first execution in the Forms subflow is the UsernamePassword provider.
This provider also does not require for a user to already be associated with the flow.
This provider creates challenge HTTP response and sets its status to challenge(). This execution is required, so the flow honors this challenge and sends the HTTP response back to the browser.
This response is a rendering of the Username/Password HTML page.
The user enters in their username and password and clicks submit.
This HTTP request is directed to the UsernamePassword provider.
If the user entered an invalid username or password, a new challenge response is created and a status of failureChallenge() is set for this execution.
A failureChallenge() means that there is a challenge, but that the flow should log this as an error in the error log.
This error log can be used to lock accounts or IP Addresses that have had too many login failures.
This provider requires that a user has been associated with the flow.
This requirement is satisfied because the UsernamePassword provider already associated the user with the flow.
Since a user is required for this provider, the provider is also asked if the user is configured to use this provider.
If user is not configured, and this execution is required, then the flow will then set up a required action that the user must perform after authentication is complete.
For OTP, this means the OTP setup page.
If the execution was optional, then this execution is skipped.
In this section, we'll take a look at the Authenticator interface.
For this, we are going to implement an authenticator that requires that a user enter in the answer to a secret question like "What is your mother's maiden name?". This example is fully implemented and contained in the examples/providers/authenticator directory of the demo distribution of Keycloak.
The classes you must implement are the org.keycloak.authentication.AuthenticatorFactory and Authenticator interfaces.
The Authenticator interface defines the logic.
The AuthenticatorFactory is responsible for creating instances of an Authenticator.
They both extend a more generic Provider and ProviderFactory set of interfaces that other Keycloak components like User Federation do.
You will package your classes within a single jar.
This jar must contain a file named `org.keycloak.authentication.AuthenticatorFactory` and must be contained in the `META-INF/services/` directory of your jar.
This file must list the fully qualified classname of each AuthenticatorFactory implementation you have in the jar.
When implementing the Authenticator interface, the first method that needs to be implemented is the requiresUser() method.
For our example, this method must return true as we need to validate the secret question associated with the user.
A provider like kerberos would return false from this method as it can resolve a user from the negotiate header.
This example, however, is validating a specific credential of a specific user.
The next method to implement is the configuredFor() method.
This method is responsible for determining if the user is configured for this particular authenticator.
For this example, we need to check if the answer to the secret question has been set up by the user or not.
In our case we are storing this information, hashed, within a UserCredentialValueModel within the UserModel (just like passwords are stored). Here's how we do this very simple check:
[source,java]
----
@Override
public boolean configuredFor(KeycloakSession session, RealmModel realm, UserModel user) {
The configuredForCredentialType() call queries the user to see if it supports that credential type.
The next method to implement on the Authenticator is setRequiredActions(). If configuredFor() returns false and our example authenticator is required within the flow, this method will be called.
It is responsible for registering any required actions that must be performed by the user.
In our example, we need to register a required action that will force the user to set up the answer to the secret question.
We will implement this required action provider later in this chapter.
Here is the implementation of the setRequiredActions() method.
[source,java]
----
@Override
public void setRequiredActions(KeycloakSession session, RealmModel realm, UserModel user) {
user.addRequiredAction("SECRET_QUESTION_CONFIG");
}
----
Now we are getting into the meat of the Authenticator implementation.
The next method to implement is authenticate(). This is the initial method the flow invokes when the execution is first visited.
What we want is that if a user has answered the secret question already on their browser's machine, then the user doesn't have to answer the question again, making that machine "trusted". The authenticate() method isn't responsible for processing the secret question form.
Its sole purpose is to render the page or to continue the flow.
[source,java]
----
@Override
public void authenticate(AuthenticationFlowContext context) {
The hasCookie() method checks to see if there is already a cookie set on the browser which indicates that the secret question has already been answered.
If that returns true, we just mark this execution's status as SUCCESS using the AuthenticationFlowContext.success() method and returning from the authentication() method.
If the hasCookie() method returns false, we must return a response that renders the secret question HTML form.
AuthenticationFlowContext has a form() method that initializes a Freemarker page builder with appropriate base information needed to build the form.
This page builder is called `org.keycloak.login.LoginFormsProvider`.
the LoginFormsProvider.createForm() method loads a Freemarker template file from your login theme.
Additionally you can call the LoginFormsProvider.setAttribute() method if you want to pass additional information to the Freemarker template.
We'll go over this later.
Calling LoginFormsProvider.createForm() returns a JAX-RS Response object.
We then call AuthenticationFlowContext.challenge() passing in this response.
This sets the status of the execution as CHALLENGE and if the execution is Required, this JAX-RS Response object will be sent to the browser.
So, the HTML page asking for the answer to a secret question is displayed to the user and the user enteres in the answer and clicks submit.
The action URL of the HTML form will send an HTTP request to the flow.
The flow will end up invoking the action() method of our Authenticator implementation.
[source,java]
----
@Override
public void action(AuthenticationFlowContext context) {
If the answer is not valid, we rebuild the HTML Form with an additional error message.
We then call AuthenticationFlowContext.failureChallenge() passing in the reason for the value and the JAX-RS response.
failureChallenge() works the same as challenge(), but it also records the failure so it can be analyzed by any attack detection service.
If validation is successful, then we set a cookie to remember that the secret question has been answered and we call AuthenticationFlowContext.success().
The last thing I want to go over is the setCookie() method.
This is an example of providing configuration for the Authenticator.
In this case we want the max age of the cookie to be configurable.
While there are four different requirement types: ALTERNATIVE, REQUIRED, OPTIONAL, DISABLED, AuthenticatorFactory implementations can limit which requirement options are shown in the admin console when defining a flow.
In our example, we're going to limit our requirement options to REQUIRED and DISABLED.
public AuthenticationExecutionModel.Requirement[] getRequirementChoices() {
return REQUIREMENT_CHOICES;
}
----
The AuthenticatorFactory.isUserSetupAllowed() is a flag that tells the flow manager whether or not Authenticator.setRequiredActions() method will be called.
If an Authenticator is not configured for a user, the flow manager checks isUserSetupAllowed(). If it is false, then the flow aborts with an error.
If it returns true, then the flow manager will invoke Authenticator.setRequiredActions().
[source,java]
----
@Override
public boolean isUserSetupAllowed() {
return true;
}
----
The next few methods define how the Authenticator can be configured.
The isConfigurable() method is a flag which specifies to the admin console on whether the Authenticator can be configured within a flow.
The getConfigProperties() method returns a list of ProviderConfigProperty objects.
These objects define a specific configuration attribute.
[source,java]
----
@Override
public List<ProviderConfigProperty> getConfigProperties() {
return configProperties;
}
private static final List<ProviderConfigProperty> configProperties = new ArrayList<ProviderConfigProperty>();
The createForm() method you called within authenticate() of your Authenticator class, builds an HTML page from a file within your login theme: `secret-question.ftl`.
This file should be added to the `theme-resources/templates` in your JAR, see <<_theme_resource,Theme Resource Provider>> for more details.
I'm hoping the UI is intuitive enough so that you can figure out for yourself how to create a flow and add the Authenticator.
After you've created your flow, you have to bind it to the login action you want to bind it to.
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.
In this section we 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.
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 `org.keycloak.authentication.RequiredActionFactory` and must be contained in the `META-INF/services/` directory of your jar.
This file must list the fully qualified classname of each RequiredActionFactory implementation you have in the jar.
But what you'll usually want to do is just add a little bit 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 both the implementation of the user profile registration processing as well as the registration Google Recaptcha plugin.
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() method of the Recaptcha plugin.
[source,java]
----
@Override
public void buildPage(FormContext context, LoginFormsProvider form) {
The Recaptcha 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.
The code above is from the registration recaptcha plugin.
Recaptcha requires some specific settings that must be obtained from configuration.
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.
Let's also look at the user profile plugin that is used to validate email address and other user information when registering.
You will package your classes within a single jar.
This jar must contain a file named `org.keycloak.authentication.FormActionFactory` and must be contained in the `META-INF/services/` directory of your jar.
This file must list the fully qualified classname of each FormActionFactory implementation you have in the jar.
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". You want your FormAction to come after user creation because the success() method of Regsitration User Creation is responsible for creating the new UserModel.
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.
Keycloak also has a specific authentication flow for forgot password, or rather credential reset initiated by a user.
If you go to the admin console flows page, there is a "reset credentials" flow.
By default, Keycloak asks for the email or username of the user and sends an email to them.
If the user clicks on the link, then they are able to reset both their password and OTP (if an OTP has been set up). You can disable automatic OTP reset by disabling the "Reset OTP" authenticator in the flow.
You can add additional functionality to this flow as well.
For example, many deployments would like for the user to answer one or more secret questions in additional to sending an email with a link.
You could expand on the secret question example that comes with the distro and incorporate it into the reset credential flow.
One thing to note if you are extending the reset credentials flow.
The first "authenticator" is just a page to obtain the username or email.
If the username or email exists, then the AuthenticationFlowContext.getUser() will return the located user.
Otherwise this will be null.
This form *WILL NOT* re-ask the user to enter in an email or username if the previous email or username did not exist.
You need to prevent attackers from being able to guess valid users.
So, if AuthenticationFlowContext.getUser() returns null, you should proceed with the flow to make it look like a valid user was selected.
I suggest that if you want to add secret questions to this flow, you should ask these questions after the email is sent.
In other words, add your custom authenticator after the "Send Reset Email" authenticator.
But the client authentication can be also used directly by you during `Direct Access grants` (represented by OAuth2 `Resource Owner Password Credentials Flow`)
or during `Service account` authentication (represented by OAuth2 `Client Credentials Flow`).
This is default mechanism mentioned in the http://openid.net/specs/openid-connect-core-1_0.html[OpenID Connect] or https://tools.ietf.org/html/rfc6749[OAuth2] specification and Keycloak supports it since it's early days.
The public client needs to include `client_id` parameter with it's ID in the POST request (so it's defacto not authenticated) and the confidential client needs to include `Authorization: Basic` header with the clientId and clientSecret used as username and password.
Authentication with signed JWT::
This is based on the https://tools.ietf.org/html/rfc7523[JWT Bearer Token Profiles for OAuth 2.0] specification.
The client/adapter generates the https://tools.ietf.org/html/rfc7519[JWT] and signs it with his private key.
The Keycloak then verifies the signed JWT with the client's public key and authenticates client based on it.
For plug your own client authenticator, you need to implement few interfaces on both client (adapter) and server side.
Client side::
Here you need to implement `org.keycloak.adapters.authentication.ClientCredentialsProvider` and put the implementation either to:
* your WAR file into WEB-INF/classes . But in this case, the implementation can be used just for this single WAR application
* Some JAR file, which will be added into WEB-INF/lib of your WAR
* Some JAR file, which will be used as jboss module and configured in jboss-deployment-structure.xml of your WAR. In all cases, you also need to create the file `META-INF/services/org.keycloak.adapters.authentication.ClientCredentialsProvider` either in the WAR or in your JAR.
Server side::
Here you need to implement `org.keycloak.authentication.ClientAuthenticatorFactory` and `org.keycloak.authentication.ClientAuthenticator` . You also need to add the file `META-INF/services/org.keycloak.authentication.ClientAuthenticatorFactory` with the name of the implementation classes.
See <<_auth_spi_walkthrough,authenticators>> for more details.