Configuring MFA in Keycloak: Enterprise Patterns
Multi-factor authentication is one of those features that every enterprise application needs, but getting the implementation right is harder than it looks. You need to balance security requirements against user experience, support multiple second factors, handle edge cases like lost devices, and make sure downstream applications can actually detect whether MFA was used.
Keycloak provides a flexible authentication flow system that handles all of this, but the configuration options are extensive and the documentation can be sparse in places. This guide walks through the practical patterns for configuring MFA in Keycloak, from basic OTP setup through conditional flows that apply MFA selectively based on roles, clients, or other conditions. For background on Keycloak SSO concepts, see the Skycloak documentation.
How Keycloak Authentication Flows Work
An authentication flow in Keycloak is an ordered tree of authenticator steps, where each step either succeeds, fails, or is skipped based on its requirement level. MFA is implemented by adding second-factor authenticator steps (OTP, WebAuthn) after the username/password step in the Browser flow.
Before configuring MFA, it helps to understand how Keycloak structures authentication.
Keycloak ships with several built-in flows:
- Browser: The standard login flow for web applications. Handles username/password entry, cookie-based session detection, and optional MFA steps.
- Direct Grant: Used for the Resource Owner Password Credentials grant (machine-to-machine or CLI logins). No browser interaction.
- Registration: Controls the user registration form.
- Reset Credentials: Handles the forgot-password flow.
Each flow is a sequence of executions, and each execution has a requirement level:
| Requirement | Behavior |
|---|---|
| Required | Must succeed for the flow to continue |
| Alternative | At least one alternative must succeed |
| Conditional | Executes only if its condition evaluates to true |
| Disabled | Skipped entirely |
MFA configuration primarily involves modifying the Browser flow to add second-factor authenticator steps after the username/password step.
The Default Browser Flow
Out of the box, Keycloak’s Browser flow looks like this:
- Cookie (Alternative) — checks for an existing session
- Identity Provider Redirector (Alternative) — handles IdP-initiated login
- Forms (Alternative) — a sub-flow containing:
- Username Password Form (Required)
- OTP Form (Optional)
The OTP Form is present but set to Optional, meaning users who have configured OTP will be prompted for it, but users without OTP configured can skip it. To enforce MFA, you need to either change this requirement or build a custom flow.
Enabling OTP as a Required Action
The simplest way to roll out MFA is to configure OTP (one-time password) as a required action for all users. This prompts every user to set up an authenticator app on their next login.
Step 1: Configure the OTP Policy
Navigate to Authentication > Policies > OTP Policy in the Keycloak admin console. Here you configure the technical parameters of OTP generation:
| Setting | Recommended Value | Notes |
|---|---|---|
| OTP Type | Time-Based (TOTP) | Preferred over counter-based (HOTP) for most use cases |
| Algorithm | SHA-1 | SHA-256 and SHA-512 are more secure but some authenticator apps do not support them |
| Number of Digits | 6 | Standard for most authenticator apps |
| Look Ahead Window | 1 | Number of intervals to check ahead/behind to handle clock skew |
| Period | 30 seconds | Standard TOTP interval |
The Look Ahead Window is worth understanding. A value of 1 means Keycloak accepts the current code, the previous code, and the next code. This accounts for clock drift between the server and the user’s device. Increasing this value makes the system more forgiving but slightly less secure.
For HOTP (counter-based), the look-ahead window determines how many counter values ahead Keycloak will check. This handles cases where a user generates codes without submitting them, causing the counter to desynchronize.
Step 2: Make OTP a Required Action
Navigate to Authentication > Required Actions. Find Configure OTP in the list and enable two settings:
- Enabled: Toggle on (allows the action to be triggered)
- Default Action: Toggle on (every new user and every user who hasn’t configured OTP will be prompted)
With Default Action enabled, the next time any user logs in, Keycloak will interrupt the login flow and present a QR code for setting up their authenticator app.
Step 3: Update the Browser Flow
To enforce OTP for every login (not just first-time setup), modify the Browser flow:
- Go to Authentication > Flows and select the Browser flow.
- Find the OTP Form execution inside the Forms sub-flow.
- Change its requirement from Optional to Required.
Now every user must provide an OTP code on every login, regardless of whether they have previously configured it.
Configuring WebAuthn / Passkeys
WebAuthn provides phishing-resistant MFA using hardware security keys (YubiKey, Titan) or platform authenticators (Touch ID, Windows Hello, Android biometrics). Keycloak has built-in support for WebAuthn as both a second factor and a passwordless primary factor. For a dedicated walkthrough of passkey setup, see enabling passkeys for 2FA in Keycloak.
Enabling WebAuthn as a Second Factor
- Navigate to Authentication > Required Actions.
- Enable Webauthn Register and set it as a Default Action if you want all users prompted to register a security key.
- Go to Authentication > Flows and select or duplicate the Browser flow.
- Inside the Forms sub-flow, add the execution WebAuthn Authenticator.
- Set its requirement to Required (or Alternative if offering it alongside OTP).
WebAuthn Policy Configuration
Navigate to Authentication > Policies > WebAuthn Policy to configure:
- Relying Party Entity Name: Your application name shown during registration (e.g., “Skycloak”)
- Signature Algorithms: ES256 is recommended (widely supported and secure)
- Attestation Conveyance Preference: Set to “none” unless you need to verify the make/model of security keys
- Authenticator Attachment: “cross-platform” for security keys, “platform” for biometrics, or leave unset for both
- Require Resident Key: Set to “No” for second-factor usage, “Yes” for passwordless flows
- User Verification Requirement: “preferred” is a good default
Offering Multiple Second-Factor Options
Most enterprise deployments want to let users choose between OTP and WebAuthn. To do this, create a sub-flow within the Forms flow:
- Inside the Forms sub-flow, add a new sub-flow called “MFA Options” and set it to Required.
- Inside MFA Options, add OTP Form as Alternative.
- Add WebAuthn Authenticator as Alternative.
This presents users with a choice. If they have both OTP and WebAuthn configured, Keycloak shows a selection screen. If they have only one configured, it goes directly to that method.
Conditional MFA with Keycloak’s Condition Authenticators
Requiring MFA for every user on every login is often too aggressive. Many organizations want MFA only for admin users, only for sensitive applications, or only when logging in from an untrusted network. Keycloak supports this through Conditional sub-flows.
How Conditional Flows Work
A conditional sub-flow contains one or more Condition authenticators followed by the actual authenticator steps. The conditions act as gates: if the condition evaluates to true, the subsequent steps execute. If false, the entire sub-flow is skipped.
Keycloak provides several built-in conditions:
- Condition – User Role: Checks if the user has a specific realm or client role
- Condition – User Configured: Checks if the user has configured a specific credential type
- Condition – User Attribute: Checks a user attribute value (available in newer Keycloak versions)
Example: MFA Only for Admin Users
Here is how to require MFA only for users with the admin role:
- Go to Authentication > Flows and duplicate the Browser flow (name it “Browser with Conditional MFA”).
- Inside the Forms sub-flow, remove the existing OTP Form.
- Add a new sub-flow called “Conditional Admin MFA” and set it to Conditional.
- Inside that sub-flow, add the execution Condition – User Role and set it to Required.
- Configure the condition: set the role to
admin(realm role) orclient-name.admin(client role). - Add the OTP Form execution inside the same sub-flow and set it to Required.
- Bind the new flow: go to Authentication > Flows, select your new flow, and click Bind flow to set it as the Browser flow.
Now, when a user logs in, Keycloak checks their roles. If they have the admin role, the OTP form appears. Otherwise, it is skipped.
Stacking Multiple Conditions
You can combine conditions by adding multiple condition authenticators to the same conditional sub-flow. All conditions set to Required must be true for the sub-flow to execute. For example, you could require MFA only for users who both have the admin role AND have already configured OTP (preventing users from being forced to set up OTP mid-login).
MFA for Specific Clients Only
One of Keycloak’s most useful features for MFA is client-level authentication flow overrides. This lets you require MFA for your internal admin dashboard while keeping the customer-facing app on simple password authentication.
Configuring Client Flow Overrides
- Create a custom browser flow with MFA required (as described in previous sections).
- Navigate to Clients and select the client that requires MFA.
- Go to the Advanced tab (or Authentication Flow Overrides section, depending on your Keycloak version).
- Under Browser Flow, select your custom MFA-enforcing flow.
- Click Save.
Now this client uses the MFA flow while all other clients continue using the realm’s default Browser flow.
This pattern works well for scenarios like:
- Internal admin tools requiring MFA, customer portals not requiring it
- High-value applications (financial, healthcare) requiring MFA while lower-risk apps skip it
- Gradual MFA rollout across applications
Example Configuration
Suppose you have three clients:
| Client | Browser Flow Override | MFA Behavior |
|---|---|---|
customer-portal |
(default) | No MFA |
admin-dashboard |
Browser with Required MFA | Always MFA |
reporting-tool |
Browser with Conditional MFA | MFA for admin role only |
Each client can reference a different authentication flow, giving you fine-grained control.
Building Custom Authentication Flows Step by Step
For complex MFA requirements, you will need to create custom authentication flows. Here is the complete process in the admin console.
Creating the Flow
- Navigate to Authentication > Flows.
- Click Create flow.
- Enter a name (e.g., “Enterprise Browser Flow”) and set the type to Basic flow.
- Click Create.
Adding Executions
The new flow starts empty. Build it up step by step:
- Click Add step and add Cookie (set to Alternative).
- Click Add step and add Identity Provider Redirector (set to Alternative).
- Click Add sub-flow, name it “Login Form” (set to Alternative).
- Inside Login Form, add Username Password Form (Required).
- Inside Login Form, add a sub-flow “MFA” (Required).
- Inside MFA, add OTP Form (Alternative).
- Inside MFA, add WebAuthn Authenticator (Alternative).
Making It Conditional
To make the MFA sub-flow conditional instead of required:
- Change the MFA sub-flow’s requirement to Conditional.
- Inside MFA, add Condition – User Role (Required) before the authenticator steps.
- Configure the condition with the desired role.
- Keep the OTP Form and WebAuthn Authenticator as Alternative.
Binding the Flow
After creating the flow, you must bind it:
- Realm-wide: On the flow’s page, click the dropdown and select Bind flow to make it the default Browser flow.
- Per-client: Go to the specific client’s Advanced settings and select the flow as the Browser Flow override.
Recovery Codes and Backup Authentication
Users inevitably lose their phones or security keys. Without backup authentication methods, they get locked out entirely. Keycloak provides several mechanisms to handle this.
Built-in Recovery Options
Keycloak does not ship with recovery codes out of the box, but you can handle recovery through a few approaches:
Admin-initiated OTP reset: An administrator can remove a user’s OTP credential from Users > [User] > Credentials, then re-add the “Configure OTP” required action. The user sets up a new authenticator on next login.
Multiple OTP devices: Users can register multiple OTP credentials. If they lose one device, they can authenticate with another. Enable this by allowing the “Configure OTP” required action to be triggered multiple times.
WebAuthn as backup: If a user has both OTP and a hardware security key registered, losing one device still leaves the other as a viable second factor.
Alternative Second Factors
For organizations that need additional MFA options beyond TOTP and WebAuthn, Keycloak’s SPI (Service Provider Interface) supports custom authenticator implementations. Common additions include:
- Email OTP: Sends a one-time code to the user’s email address. Skycloak’s managed Keycloak platform includes an Email OTP extension pre-installed, adding email-based verification as a second factor option without custom development. See using the Email OTP extension with Skycloak for a setup guide.
- SMS OTP: Sends codes via SMS (requires a custom authenticator or third-party extension).
- Push notifications: Integrates with push notification services for approve/deny MFA prompts.
These extensions plug into the same authentication flow system, so they can be added as Alternative executions alongside OTP and WebAuthn.
Detecting MFA Status in Application Tokens
Configuring MFA in Keycloak is only half the story. Your applications need to know whether a user authenticated with MFA so they can make authorization decisions accordingly, such as allowing access to sensitive operations only after MFA.
The acr Claim
Keycloak can include the Authentication Context Class Reference (acr) claim in ID tokens and access tokens. This claim indicates the level of authentication that was performed.
By default, Keycloak maps:
acr: "0"— single-factor authenticationacr: "1"— multi-factor authentication
Configuring ACR in Client Scopes
To include the acr claim in tokens:
- Navigate to Client Scopes and find the
acrscope (available by default in recent Keycloak versions). - Ensure it is assigned to your client as a default scope.
- The
acrclaim mapper uses the Keycloak ACR to LoA mapping to determine the value.
Checking MFA Status in Your Application
Here is how to check the acr claim in a decoded JWT token (you can paste tokens into the JWT Token Analyzer to inspect claims interactively):
{
"sub": "f1b2c3d4-5678-9abc-def0-1234567890ab",
"email": "[email protected]",
"acr": "1",
"amr": ["pwd", "otp"],
"aud": "my-application",
"iss": "https://auth.example.com/realms/my-realm"
}
The amr (Authentication Methods References) claim lists the specific methods used. In this example, the user authenticated with a password (pwd) and a one-time password (otp).
In a Node.js application, you might enforce MFA for sensitive operations like this:
const jwt = require('jsonwebtoken');
function requireMfa(req, res, next) {
const token = req.headers.authorization?.split(' ')[1];
if (!token) {
return res.status(401).json({ error: 'No token provided' });
}
const decoded = jwt.decode(token);
// Check the acr claim for MFA
if (decoded.acr !== '1') {
return res.status(403).json({
error: 'Multi-factor authentication required',
hint: 'Re-authenticate with MFA to access this resource'
});
}
next();
}
// Apply to sensitive routes
app.post('/api/billing/update', requireMfa, billingController.update);
app.delete('/api/users/:id', requireMfa, userController.delete);
In a Java Spring application using Spring Security and the Keycloak adapter:
@Component
public class MfaAuthorizationFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain chain)
throws ServletException, IOException {
Authentication auth = SecurityContextHolder.getContext()
.getAuthentication();
if (auth instanceof JwtAuthenticationToken jwtAuth) {
String acr = jwtAuth.getToken()
.getClaimAsString("acr");
if (!"1".equals(acr)) {
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
response.getWriter().write(
"{"error": "MFA required for this operation"}"
);
return;
}
}
chain.doFilter(request, response);
}
}
Step-Up Authentication with ACR Values
For applications that need to request a specific authentication level, you can use the acr_values parameter in the OpenID Connect authorization request:
GET /realms/my-realm/protocol/openid-connect/auth
?client_id=my-app
&response_type=code
&scope=openid
&acr_values=1
&redirect_uri=https://app.example.com/callback
When Keycloak receives acr_values=1, it ensures the user completes MFA before issuing tokens. If the user already has an active session but did not use MFA, Keycloak will prompt for the second factor.
This enables step-up authentication: a user can browse low-sensitivity pages with single-factor auth, and when they try to access a sensitive page, your application redirects them to Keycloak with acr_values=1 to force MFA.
OTP Policy Tuning for Enterprise Environments
The default OTP policy works for most cases, but enterprise environments often have specific requirements.
Algorithm Selection
| Algorithm | Compatibility | Security |
|---|---|---|
| SHA-1 | Supported by all major authenticator apps | Adequate for TOTP (used with HMAC, not raw hashing) |
| SHA-256 | Google Authenticator (recent versions), Authy, 1Password | Better theoretical security margin |
| SHA-512 | Limited app support | Overkill for 6-digit codes |
If your organization standardizes on a specific authenticator app, check which algorithms it supports before changing from the SHA-1 default. Switching algorithms after users have enrolled means they need to re-register their authenticator.
For details on how authentication errors are presented to users during failed MFA attempts, see our guide on authentication error handling in Keycloak.
Longer Codes for High-Security Environments
Increasing from 6 to 8 digits raises the brute-force difficulty from 1-in-a-million to 1-in-100-million. The tradeoff is user friction — 8-digit codes are harder to type quickly. For most organizations, 6 digits with rate limiting on failed attempts provides sufficient security.
Adjusting the Time Period
The default 30-second window is standard, but some environments adjust it:
- 15 seconds: Higher security, but users have less time to type the code. Can cause problems with slow typists or accessibility needs.
- 60 seconds: More forgiving, but each code is valid for longer. Consider this for environments with known clock synchronization issues.
Putting It All Together: An Enterprise MFA Architecture
Here is a complete example combining the patterns discussed above for a typical enterprise deployment:
Realm-level defaults:
- OTP Policy: TOTP, SHA-1, 6 digits, 30-second period
- WebAuthn enabled as an alternative second factor
- Default Browser flow: username/password only (no MFA)
Client overrides:
customer-portal: Uses default flow (no MFA)admin-console: Custom flow with required MFA (OTP or WebAuthn)finance-app: Custom flow with conditional MFA (required forfinance-adminrole, optional for others)
Token-level enforcement:
- All clients include the
acrscope - Sensitive API endpoints check
acrclaim before processing requests - Step-up authentication flow redirects users to Keycloak with
acr_values=1when accessing high-risk operations
Recovery:
- Users encouraged to register both an authenticator app and a hardware key
- Email OTP available as a backup second factor (via Skycloak’s Email OTP extension on the managed platform)
- Admins can reset user credentials and re-trigger OTP enrollment through the admin console
This layered approach provides strong security for sensitive resources without burdening every user on every login.
Managed MFA with Skycloak
Configuring MFA in Keycloak is straightforward for basic scenarios, but enterprise deployments often run into operational complexity: keeping OTP policies consistent across environments, managing WebAuthn relying party configuration, deploying custom authenticator extensions, and handling the inevitable support tickets from users who lose their devices.
Skycloak’s managed Keycloak platform handles this operational overhead. MFA features including OTP, WebAuthn, and Email OTP come pre-configured and ready to use. Authentication flows can be customized through the standard Keycloak admin console, with the underlying infrastructure, updates, and high availability managed for you.
If you are evaluating MFA options for your Keycloak deployment, take a look at Skycloak’s pricing plans to see how managed Keycloak can simplify your authentication infrastructure.
Ready to simplify your authentication?
Deploy production-ready Keycloak in minutes. Unlimited users, flat pricing, no SSO tax.