256 lines
17 KiB
Text
256 lines
17 KiB
Text
[[_authentication-flows]]
|
|
|
|
=== Authentication Flows
|
|
|
|
An _authentication flow_ is a container for all authentications, screens, and actions that must happen during login, registration, and other
|
|
{project_name} workflows.
|
|
If you go to the admin console `Authentication` left menu item and go to the `Flows` tab, you can view all the defined flows
|
|
in the system and what actions and checks each flow requires.
|
|
|
|
==== Built-in flows
|
|
|
|
{project_name} comes with a certain number of built-in flows. These flows cannot be modified, but the requirements can be modified to
|
|
suit your needs.
|
|
|
|
This section does a walk-through of the built-in browser login flow. In the
|
|
left drop-down list select `browser` to come to the screen shown below:
|
|
|
|
.Browser Flow
|
|
image:{project_images}/browser-flow.png[]
|
|
|
|
If you hover over the tooltip (the tiny question mark) to the right of the flow selection list, this will describe what the
|
|
flow is and does.
|
|
|
|
The `Auth Type` column is the name of authentication or action that will be executed. If an authentication is indented
|
|
this means it is in a sub-flow and may or may not be executed depending on the behavior of its parent. The `Requirement`
|
|
column is a set of radio buttons which define whether or not the action will execute. Let's describe what each radio
|
|
button means in this context:
|
|
|
|
Required::
|
|
This authentication execution must execute successfully. If the user doesn't have that type of authentication mechanism
|
|
configured and there is a required action associated with that authentication type, then a required action will be attached
|
|
to that account. For example, if you switch the `Browser - Conditional OTP` sub-flow to `Required`, users that don't have an OTP generator configured
|
|
will be asked to do so.
|
|
Alternative::
|
|
This means that at least one alternative authentication type must execute successfully at that level of the flow.
|
|
Disabled::
|
|
If disabled, the authentication type is not executed.
|
|
Conditional::
|
|
This requirement type can only be set on sub-flows, indicating that it will only be executed if its condition evaluates to true.
|
|
|
|
This is better described in an example. Let's walk through the `browser` authentication flow.
|
|
|
|
. The first authentication type is `Cookie`. When a user successfully logs in for the first time, a session cookie is set.
|
|
If this cookie has already been set, then this authentication type is successful. In this case,
|
|
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.
|
|
. The second execution of the flow looks at the `Kerberos` execution. This authenticator is disabled by default and will be skipped.
|
|
. The third execution is the `Identity Provider Redirector`. It can be configured through the `Actions` > `Config` link to automatically redirect to another IdP for <<_identity_broker, identity brokering>>.
|
|
. The next execution is a sub-flow called `Forms`. Since this sub-flow is marked as _alternative_ it will not be executed if the `Cookie` authentication type passed.
|
|
This sub-flow contains additional authentication type that needs to be executed.
|
|
The executions for this sub-flow are loaded and the same processing logic occurs.
|
|
. The first execution in the Forms sub-flow is the Username Password Form. This authentication type renders the username and password page.
|
|
It is marked as _required_ so the user must enter in a valid username and password.
|
|
. The second execution in the Forms sub-flow is a new sub-flow: the `Browser - Conditional OTP` sub-flow. Since this sub-flow is _conditional_, whether it is executed depends on the result of the
|
|
evaluation of the `Condition - User Configured` execution. If it is, the executions for this sub-flow are loaded and the same processing logic occurs
|
|
. The next execution is the `Condition - User Configured`. This checks if the other executions in the flow are configured for the user.
|
|
Meaning that the `Browser - Conditional OTP` sub-flow will only be executed if the user has an OTP credential configured.
|
|
. The final execution is the `OTP Form`. This is marked as _required_, but because of the setup in the _conditional_ subflow, it will only be run if the user
|
|
has an OTP credential set up. If he doesn't, the user will not see an OTP form.
|
|
|
|
==== Creating flows
|
|
|
|
This section explains in greater depth how flows work, and how to create your own flows. Note that there are important functionality and security
|
|
considerations when designing your own flow. A badly created flow could either let no one log in, let users in with less verification than you would
|
|
like, or simply result in an error.
|
|
|
|
To create a flow, you can either:
|
|
. Copy and then modify an existing flow. To do this select an existing flow (for example the `Browser` flow), and press the `Copy` button.
|
|
This will then ask you to set a name for the new flow, before creating it.
|
|
. Create a new flow from scratch. To do this press the `New` button. Since this is the more general case, we will use this for our example.
|
|
|
|
When creating a new flow, you will have to create a top level flow
|
|
|
|
.Create a top level flow
|
|
image:{project_images}/Create-top-level-flow.png[]
|
|
|
|
With the following options:
|
|
|
|
Alias::
|
|
The name of the flow.
|
|
Description::
|
|
The description you can set to the flow.
|
|
Top Level Flow Type::
|
|
The type of flow. the type `client` is used only for the authentication of clients (applications). For all other cases choose `generic`.
|
|
|
|
Once the flow is created, in addition to the `New` and `Copy` buttons, you now have, `Delete`, `Add execution` and `Add flow`.
|
|
|
|
.An empty new flow
|
|
image:{project_images}/New-flow.png[]
|
|
|
|
What a flow finally does is determined by the structure of the flow and sub-flows, the executions in those flows, and the requirements set on the
|
|
sub-flows and the executions.
|
|
|
|
Executions can be added with the `Add execution` button. Executions can have a wide variety of actions, from sending a reset email to validating an OTP. If you hover over the
|
|
tooltip (the tiny question mark) next to `Provider`, this will describe what the execution does.
|
|
|
|
.Adding an authentication execution
|
|
image:{project_images}/Create-authentication-execution.png[]
|
|
|
|
These can be divided into _automatic executions_ and _interactive executions_. _Automatic executions_ are similar to the `Cookie` execution, and will automatically
|
|
perform their action when they are encountered in the flow. _Interactive executions_ will halt the flow, usually to get some user input. Executions that execute
|
|
successfully will get the _success_ status. This is important, because this is part of whether a flow is successful or not. For example, an empty `Browser` flow
|
|
would not allow anyone to log in. For that it would need at least one execution that successfully evaluates, for example a `Username Password Form` that is corrected
|
|
filled and submitted.
|
|
|
|
Sub-flows can be added in top level flow with the `Add flow` button, which opens a `Create Execution Flow` page that is very similar to the `Create Top Level Form`
|
|
page. The only difference is that the `Flow Type` can be either `generic` (like before), or `form`. The `form` type is used to construct a sub-flow that generates
|
|
a single form for the user, like what is done for the built-in `Registration` flow. Sub-flows are a special type of execution that evaluate as successful
|
|
depending on how the executions they contain evaluate (and this includes the evaluation of their contained sub-flows). And the logic of this evaluation
|
|
depends on the Requirement of each execution and sub-flow.
|
|
|
|
Fully understanding this requires a more complete explanation of how requirements work when evaluating a flow, and this also applies to sub-flows:
|
|
|
|
Required::
|
|
For a flow to be evaluated as successful, all required elements in the flow must evaluate as successful. This means that all _Required_ elements in the flow
|
|
must be sequentially executed, from top to bottom, unless one the the elements causes the flow to fail. However, this is only true for the current flow.
|
|
Any _Required_ element within a sub-flow is only processed if that sub-flow is entered.
|
|
Alternative::
|
|
When a flow contains only _Alternative_ elements, only a single element must evaluate as successful for the flow to evaluate as successful.
|
|
Because the _Required_ flow elements within a flow are sufficient to mark a flow as successful, any _Alternative_ flow element within a flow
|
|
that contains _Required_ flow elements will never be executed. In this case, they are functionally _Disabled_.
|
|
Disabled::
|
|
Any _Disabled_ element is not evaluated and does not count to mark a flow as successful.
|
|
Conditional::
|
|
This requirement type can only be set on sub-flows. A _Conditional_ sub-flow can contain a "Condition" execution. These "Condition" executions must evaluate as
|
|
logical statements. If all "Condition" executions evaluate as _true_ then the _Conditional_ sub-flow acts as _Required_. If not, the _Conditional_ sub-flow
|
|
acts as _Disabled_. If no "Condition" execution is set, the _Conditional_ sub-flow acts as _Disabled_. If a flow contains "Condition" executions and is not set to
|
|
_Conditional_, the "Condition" executions are not evaluated, and can be considered functionally _Disabled_.
|
|
|
|
Note that after adding an execution, you should check that the Requirement is set to the correct value. Even if there is only a single possible Requirement, it
|
|
can happen that it is not set.
|
|
|
|
When constructing a flow, all elements added to the flow will have an `Actions` menu on the right-hand side. All elements added to the flow have a `Delete`
|
|
option in this menu to remove it from the flow. Executions can contain a `Config` menu option to configure the execution, as is the case for the
|
|
`Identity Provider Redirector`. Sub-flows can also have executions and sub-flows added to them, with their `Add execution` and `Add flow` menu options.
|
|
|
|
Finally, since the order of execution is important, you can move executions and sub-flows up and down within their respective flows with the up and down buttons
|
|
that are set to left of their name.
|
|
|
|
==== Creating a password-less browser login flow
|
|
|
|
To illustrate the creation of flows, this section describes the creation of a more advanced browser login flow. The purpose of this flow is to allow a
|
|
user to choose between logging in in a password-less manner using <<_webauthn, WebAuthn>>, and a two-factor authentication with password and OTP.
|
|
The flow to create is similar to the standard browser login, but diverges when reaching the username selection. Instead of copying the flow however, you'll be
|
|
creating the flow from the start:
|
|
|
|
* Select a realm, click on Authentication link
|
|
* Select "new", and give the new flow a distinctive Alias, i.e. "Browser Password-less"
|
|
* Select "Add execution", and using the drop-down select "Cookie". After pressing "Save", set its Requirement to _Alternative_.
|
|
* Select "Add execution", and using the drop-down select "Kerberos".
|
|
* Select "Add execution", and using the drop-down select "Identity Provider Redirector". After pressing "Save", set its Requirement to _Alternative_.
|
|
* Select "Add flow", and choose an representative Alias, e.g. "Forms". After pressing "Save", set its Requirement to _Alternative_.
|
|
|
|
.The common part with the browser flow
|
|
image:images/Passwordless-browser-login-common.png[]
|
|
|
|
* Using the `Actions` menu on the right-hand side of the "Forms" subflow, select "Add execution". Using the drop-down select
|
|
"Username Form". After pressing "Save", set its Requirement to _Required_.
|
|
|
|
The Username form is similar to "Browser" flow's Username Password Form, but only asks for a username, allowing a user to do perform a password-less login.
|
|
However, note that this inevitably allows a user enumeration attack on your {project_name} server. This is an unavoidable security risk for the convenience,
|
|
so the flow should make sure that an attacker cannot just have to guess a password to be able to enter.
|
|
|
|
* Using the `Actions` menu on the right-hand side of the "Forms" subflow, select "Add flow". Choose an representative Alias, e.g. "Authentication".
|
|
After pressing "Save", set its Requirement to _Required_.
|
|
* Using the `Actions` menu on the right-hand side of the "Authentication" subflow, select "Add execution". Using the drop-down select
|
|
"Webauthn Authenticator". After pressing "Save", set its Requirement to _Alternative_.
|
|
* Using the `Actions` menu on the right-hand side of the "Authentication" subflow, select "Add flow". Choose an representative Alias, e.g. "Password with OTP".
|
|
After pressing "Save", set its Requirement to _Alternative_.
|
|
* Using the `Actions` menu on the right-hand side of the "Password with OTP" subflow, select "Add execution". Using the drop-down select
|
|
"Password Form". After pressing "Save", set its Requirement to Required.
|
|
* Using the `Actions` menu on the right-hand side of the "Password with OTP" subflow, select "Add execution". Using the drop-down select
|
|
"OTP Form". After pressing "Save", set its Requirement to Required.
|
|
* In the "Bindings" menu, change the browser flow from "Browser" to "Browser Password-less"
|
|
|
|
The final flow that is produced is the following:
|
|
|
|
.A password-less browser login
|
|
image:images/Passwordless-browser-login.png[]
|
|
|
|
After entering the username, the way this flow works is the following:
|
|
|
|
* If the user has any WebAuthn credentials recorded, he will be able to use any of them to log in directly. This is the password-less login.
|
|
The user also has a drop-down allowing him to select "Password with OTP". He can do this because the "WebAuthn" execution and the "Password with OTP"
|
|
flow are set to _Alternative_. Were they set to _Required_ the user would have to enter WebAuthn, password, and OTP.
|
|
* If the user selects the "Password with OTP" from the drop-down, or if the user doesn't have any WebAuthn credentials, he will have to first enter his
|
|
password, and then his OTP. If the user has no OTP credential, he will be asked to record one.
|
|
|
|
It is important to note that since the WebAuthn execution is set to _Alternative_ instead of _Required_, this flow will never ask the user to register a WebAuthn credential. For a user
|
|
to have a Webauthn credential, he must have a required action added to him by an administrator. This is done first by making sure that that the `Webauthn Register`
|
|
required action is enabled in the realm (see the <<_webauthn,WebAuthn>> documentation), and then by setting the required action by using the `Credential Reset` part of a
|
|
user's <<_user-credentials,Credentials>> management menu.
|
|
|
|
Creating a more advanced flow such as this one can have some subtle side effects. For example, if you were to enable the ability to reset the password
|
|
for the user, then this would be accessible from the password form. In the default "Reset Credentials" flow, the user has to enter his username. Since
|
|
he's already entered his username earlier in the "Browser Password-less" flow, this would be unnecessary for {project_name}, and a sub-optimal in terms of user
|
|
experience. To correct this, you could:
|
|
|
|
* Copy the "Reset Credentials" flow, setting its name to, for example "Reset Credentials for password-less"
|
|
* Use the `Actions` menu on the right-hand side of the "Choose user" execution, select "Delete"
|
|
* In the "Bindings" menu, change the reset credential flow from "Reset Credentials" to "Reset Credentials for password-less"
|
|
|
|
ifeval::[{project_community}==true]
|
|
=== Script Authenticator
|
|
A _script_ authenticator allows to define custom authentication logic via JavaScript.
|
|
Custom authenticators. In order to make use of this feature, it must be explicitly enabled:
|
|
[source]
|
|
----
|
|
bin/standalone.sh|bat -Dkeycloak.profile.feature.scripts=enabled
|
|
----
|
|
For more information, see the link:{installguide_profile_link}[{installguide_profile_name}] section.
|
|
|
|
Authentication scripts must at least provide one of the following functions:
|
|
`authenticate(..)` which is called from `Authenticator#authenticate(AuthenticationFlowContext)`
|
|
`action(..)` which is called from `Authenticator#action(AuthenticationFlowContext)`
|
|
|
|
Custom `Authenticator` should at least provide the `authenticate(..)` function.
|
|
The following script `javax.script.Bindings` are available for convenient use within script code.
|
|
|
|
`script`::
|
|
the `ScriptModel` to access script metadata
|
|
`realm`::
|
|
the `RealmModel`
|
|
`user`::
|
|
the current `UserModel`
|
|
`session`::
|
|
the active `KeycloakSession`
|
|
`authenticationSession`::
|
|
the current `AuthenticationSessionModel`
|
|
`httpRequest`::
|
|
the current `org.jboss.resteasy.spi.HttpRequest`
|
|
`LOG`::
|
|
a `org.jboss.logging.Logger` scoped to `ScriptBasedAuthenticator`
|
|
|
|
Note that additional context information can be extracted from the `context` argument passed
|
|
to the `authenticate(context)` `action(context)` function.
|
|
|
|
[source,javascript]
|
|
----
|
|
AuthenticationFlowError = Java.type("org.keycloak.authentication.AuthenticationFlowError");
|
|
|
|
function authenticate(context) {
|
|
|
|
LOG.info(script.name + " --> trace auth for: " + user.username);
|
|
|
|
if ( user.username === "tester"
|
|
&& user.getAttribute("someAttribute")
|
|
&& user.getAttribute("someAttribute").contains("someValue")) {
|
|
|
|
context.failure(AuthenticationFlowError.INVALID_USER);
|
|
return;
|
|
}
|
|
|
|
context.success();
|
|
}
|
|
----
|
|
endif::[]
|