Keycloak Step-Up and Multi-factor Authentication (MFA) for Web Apps and API
In Short
The focus of the article is to announce that finally Keycloak (Identity and Access Management) will support step-up authentication. With step-up authentication, Web Applications or APIs that allow access to different types of resources can require users to authenticate with a stronger authentication mechanism to access sensitive resources.
Update: July 21, 2023 😀
Now you have access to the workshop for Keycloak Workshop for Step Up with MFA Biometrics Authentication (Passkeys) and Passwordless login experience on Github, which includes the use cases described in this article. Additionally, there is a new article 🙌 with more information and cool new use cases, such as, how to implement Step-up Authentication based on OAuth 2.0 Step-up Authentication Challenge Protocol and Passkeys for 1 or 2-factor (MFA).
Update: January 4, 2024
I have updated the article to include the use of the Authentication Methods Reference because it will be supported in 🙌 Keycloak version 24. Since this version has not been released yet (the PR was merge into the main branch), I have built Keycloak from source in order to test the functionality for the article.
Update: January 26, 2024
There is a new way to handle the authentication and step-up experience with native authentication in Keycloak. It’s an API-based approach that enhances the UX by delegating the control of the authentication steps to the application. If you are interested, here is my new article that shows this approach in detail.
I won’t dive deep into the explanation about step-up authentication or the standard involved because there is a lot of information available. I will just described a high-level overview technical points:
- An Authentication Context Class (
acr
claim) specifies a set of business rules that authentications are being requested to be satisfied. ACR is referred to as the level of assurance (LoA) or Authenticator Assurance Level (AAL). Authentication Requests using theacr_values
parameter request that the specified Authentication Context will be used. - Authentication Methods Reference (
amr
claim) is an JSON array of strings that are identifiers for authentication methods used in
the authentication. According to the RFC 8176, you can specify for instance the authentication method such as pwd (password), otp, user, etc to give more information to the application after the authentication process has concluded. - To fully support the step-up, a client should be able to force re-authentication even when the IdP considers that a user is authenticated to an acceptable level. In the OpenID Connect standard, this is done with the
prompt=login
optional parameter.
At the moment I was writing the article the PR keycloak-847 was approved and merged into main repository. Now, it’s officially available.
New features
Clients: ACR to Level of Authentication Mapping
Clients > Advance Settings > ACR to Level of Authentication Mapping
You can define which Authentication Context Class Reference (ACR) value is mapped to which Level of Authentication (LoA). The acr
claim will be included in the access token and ID token.
For instance:
In this example the acr
aal1 is related to one-factor authentication, therefore, the user authenticates with username and password. On the other hand, the acr
aal2 is related to two-factor authentication, so it requires the authentication with password and TOTP or Passkeys.
I choose these values to simplify the demo, but for specifying MFA you can use the valuehttp://schemas.openid.net/pape/policies/2007/06/multi-factor
(based on OpenID Provider Authentication Policy Extension) or http://schemas.openid.net/policies/modrna/multi-factor
(based on OpenID Connect MODRNA Authentication Profile 1.0).
Flow: Condition - Level Of Authentication
Realm > Flows > Condition - Level Of Authentication
Based on the browser login which is an authentication flow you can use the new flow “Condition - Level Of Authentication” in order to require or not the defined authentication step based on the value of parameters acr_values
specified by the application. By doing so, the client could specify the authentication level of a user.
I have also associated the authentication mechanism reference with the desired amr
claim values to provide the application with more context about the login process. In this case: Password Form → amr: pwd, OTP Form → amr: otp, and WebAuthn → amr: user. Here is an example for the OTP form.
Example Step-Up Use Case
In this scenario I’ve registered a demo realm and I’ve created an OAuth Client for the OIDC demo Bank Portal. I’ve also followed the step-up configuration described before.
In the following sequence diagram you will see that the Bank portal for the sign-in processs will require one factor authentication (username and password) and for managing the bank accounts two factor authentication (mfa).
STEP #1: In this case the web application wants to authenticate the user with username and password (one factor authentication). The application is configured to authenticate with OpenID Connect and it will implement the OAuth Flow Authorization Code.
To initiate the authentication process the web application must send the authentication request specifying the required acr_values
, in this example, aal1
.
https:/idp/auth/realms/Demo/protocol/openid-connect/auth?
response_type=code&
client_id=spa&
scope=openid%20profile&
state=Xvg9WF-dpnoLGWDocd35PWRf7pHmqpc&
redirect_uri=https://webapp/callback&
nonce=Gwh7EDGaKUnOa1CYLJXwNTlufGLqKU&
acr_values=aal1
Keycloak based on the configuration that I’ve described before will follow these authentication steps:
- Prompt for username
- Prompt for password (one factor authentication)
- Skip second factor authentication step
Keycloak after the successful authentication will return the authorization code to the application and then the application will negotiate the tokens.
Here is an example with id_token (payload decoded):
Based on the acr
claim it will know the level of assurance of the authenticated user and the authentication mechanism amr
claim.
STEP #2: If the application wants to do a step-up authentication for requiring two-factor authentication it must send the acr_values
with the value aal2
- it also could specify the parameter prompt_login
with the value true
to force the authentication. Here’s an example:
https:/idp/auth/realms/Demo/protocol/openid-connect/auth?
response_type=code&
client_id=spa&
scope=openid%20profile&
state=Lll923-dpnoLGWDocd35PWRf7pHmqpc-DxQNdTEdy-U%3D&
redirect_uri=https://webapp/callback
nonce=Gwh7EDGaKUnOa1CYLJXwNTlufGLqKUkB1URdJBFbClg&
acr_values=aal2&
prompt_login=true
Keycloak will follow these authentication steps:
- Prompt for username
- Prompt for password (first factor authentication)
- Prompt for otp (second factor authentication)
Here is an example with id_token:
Based on the acr
claim, it will indicate the level of assurance of the authenticated user; in this case, ‘aal2,’ and the amr
claim that contains ‘otp’ indicating the use of the second factor.”
UPDATED: When I originally wrote this article, I used OTP as the second factor, but nowadays, you can use Passkeys that trigger Touch or Face ID. If you want to know more about it, check out the new article 🙌.
BONUS: Step-Up authentication for API
You can also implement step-up authentication to your API protected by OAuth. For instance, the API can verify that the user has logged in using MFA and, if not, require the user to step-up to access certain resources. Therefore, you can use the acr
claim which is also available in the access token.
In this example, for second factor authentication the access token has the acr
claim with the value aal2
:
The API will validate if the acr
claim is equal to the value aal2
in the access token received in the Authorization Header with format: Bearer {access-token} based on RFC 6750.
Here is an authorization rule in Rego language for Open Policy Agent. OPA is an open-source policy engine that provides a simple API for delegating policy decisions to it. General-purpose policy engine that unifies policy enforcement across the stack. In this example the policy requires multi-factor authentication (claim is equal to aal2
) for payment operations (POST
/api/v1/payment).
package http.authz
default allow = false## Check if claim acr is equal to 'aal2' for POST payments
allow {
input.method == "POST"
input.path[_] == "payment"
token.payload.acr == "aal2"
}## Decode and extract JWT payloadtoken = {"payload": payload }{
auth_header := input.headers.authorization
startswith(auth_header, "Bearer ")
bearer_token := substring(auth_header, count("Bearer "), -1)
[_, payload, _] := io.jwt.decode(bearer_token)
}
UPDATED: Nowadays, the OAuth 2.0 Step-up Authentication Challenge Protocol standard (RFC 9470) is available - thanks to 🖤 Vittorio. This enables the API to implement step-up authentication by understanding the required Authentication Context Level (acr). If the level is not sufficient, the API informs the client that it needs to trigger step-up authentication. If you want to know more, check out the new article Step Up with MFA Biometrics Authentication (Passkeys) and Passwordless login experience.
Conclusion
At first sight the implementation looks very promising and you can implement step-up for Web Apps and API without any problem. I’ve reviewed this feature in other IAM platforms and Keycloak is one of the first ones to implement the RFC 9068 in order to have the acr claim present in the access token. It is the key for applying access control based on the authentication level of assurance in the API scenario.
Thanks to Keycloak’s support for the Authentication Methods Reference (amr
claim) in complex use cases, it is helpful to indicate to the application the authentication method used. For complex login journeys, you can use the amr
claim with the value ‘mfa,’ confirming to the application that the user went through a 2-step authentication process.
Finally, with the OAuth 2.0 Step-up Authentication Challenge Protocol, you can improve the security and user experience on the API side by using the acr
and amr
claims to check the desire level of authentication. If the level is not sufficient, you can trigger the step-up process following the step-up challenge standard RFC 9470.
That’s all for this post!
Thank you for reading. If you like it, then share it!
👉 Lastly, check the new article and github workshop about Step Up with MFA Biometrics Authentication (Passkeys) and Passwordless login experience.