Keycloak Step-Up and Multi-factor Authentication (MFA) for Web Apps and API

Martin Besozzi
8 min readJan 19, 2022

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.

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 the acr_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:

ACR to LoA mapping

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.

Keycloak authentication flow MFA

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.

Keycloak AMR configuration for OTP step

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).

Sequence Diagram for Step-Up process

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:

  1. Prompt for username
  2. Prompt for password (one factor authentication)
  3. Skip second factor authentication step
Sign In with 1 factor (ACR value = aal1)
1 Factor - Prompt Username
1 Factor - Prompt Password

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):

ID token information

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)
Sign In with 2 factor (ACR value = aal2)
1 Factor — Prompt Username
1 Factor — Prompt Password
2 Factor — Prompt for OTP

Here is an example with id_token:

ID token information

Based on the acrclaim, 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.

--

--

Martin Besozzi

IAM Architect and Java Developer. I have been working for 16 years focused on IAM Space, Digital User, API Authz. #Keycloak #ForgeRock Twitter / GH: @embesozzi