Multi-Factor Authentication Setup
Multi-Factor Authentication (MFA) adds an extra layer of security by requiring users to provide multiple forms of verification. This guide covers setting up various MFA methods in Skycloak.
Overview
MFA requires users to provide two or more verification factors:
- Something you know (password)
- Something you have (phone, hardware token)
- Something you are (biometrics)
Available MFA Methods
Time-Based One-Time Password (TOTP)
The most common MFA method using authenticator apps.
Supported Apps:
- Google Authenticator
- Microsoft Authenticator
- Authy
- 1Password
- LastPass Authenticator
SMS-Based OTP
Send one-time codes via SMS (requires SMS gateway configuration).
Email-Based OTP
Send one-time codes via email (requires SMTP configuration).
WebAuthn/FIDO2
Hardware security keys and platform authenticators.
Supported Devices:
- YubiKey
- Google Titan Security Key
- Windows Hello
- Touch ID / Face ID
Enabling MFA for a Realm
Basic Setup
- Navigate to your realm in Skycloak
- Go to Authentication → Required Actions
- Enable desired MFA methods:
- Configure OTP for TOTP
- WebAuthn Register for security keys
- Update Password (if forcing password reset)
Authentication Flow Configuration
- Go to Authentication → Flows
- Select Browser flow
- Add execution:
- Click Add execution
- Select OTP Form or WebAuthn Authenticator
- Set to Required or Alternative
TOTP Configuration
Enabling TOTP
- In Authentication → Required Actions
- Enable Configure OTP
- Set as Default Action if needed
TOTP Policy Settings
Navigate to Authentication → OTP Policy:
OTP Type: Time-based (totp)
OTP Hash Algorithm: SHA256
Number of Digits: 6
Look Ahead Window: 1
OTP Token Period: 30User Enrollment Flow
- User logs in with username/password
- Prompted to set up MFA
- Scan QR code with authenticator app
- Enter verification code
- Save recovery codes
WebAuthn/FIDO2 Setup
Enabling WebAuthn
- Go to Authentication → Required Actions
- Enable WebAuthn Register
- Configure WebAuthn Policy
WebAuthn Policy
Navigate to Authentication → WebAuthn Policy:
Relying Party Entity Name: Your Company
Signature Algorithms: ES256, RS256
Relying Party ID: yourdomain.com
Attestation Conveyance Preference: none
Authenticator Attachment: platform, cross-platform
Require Resident Key: No
User Verification Requirement: preferredPasswordless Setup
For passwordless authentication:
- Create new authentication flow
- Add WebAuthn Passwordless Authenticator
- Set as alternative to username/password
SMS OTP Configuration
Prerequisites
- Configure SMS gateway provider
- Set up SMS authenticator SPI
Setup Steps
- Install SMS authenticator extension
- Configure provider settings:
Provider: Twilio/AWS SNS/Custom API Key: your-api-key API Secret: your-api-secret From Number: +1234567890 - Enable in authentication flow
Conditional MFA
Risk-Based MFA
Keycloak on its own can ask for a second factor on every login or never. The free Adaptive Risk extension asks only when a login looks unusual for that user. It scores each browser login from 0 to 100 against the user’s own past successful logins, names the reasons, and maps the score to a level:
| Level | Default score | Typical response |
|---|---|---|
| Low | below 30 | Password only |
| Medium | 30 to 59 | OTP or WebAuthn, for users who already have one |
| High | 60 and above | Deny access |
The reasons and their default weights are new_device (30), new_network (15), new_country (30), rapid_country_change (40), recent_failures (25) and unusual_hour (10). The score is their sum, capped at 100. Until a user has 3 successful logins the profile is learning: history reasons do not fire, and the login is scored as low unless there were recent failed attempts. Thresholds, weights and the number of learning logins can be changed with the gear icon on the evaluator step.
Install the extension
- Open your cluster in the Skycloak dashboard and go to Extensions
- Find Adaptive Risk and click Install
- Keep the pre-filled Client IP Header (
CF-Connecting-IP) and Country Header (CF-IPCountry), then click Install Extension
Clear Client IP Header to use the client address Keycloak resolves itself. Clear Country Header to turn the two country reasons off. Only name headers your proxy always overwrites, because a header the client can set can be forged.
Build the flow
- Go to Authentication > Flows, duplicate the built-in browser flow, and remove its Conditional OTP sub-flow
- In the forms sub-flow, after Username Password Form, add Adaptive Risk - Evaluate (Skycloak) as Required
- Still in forms, add a Conditional sub-flow Deny high risk containing:
-
Condition - risk level (Skycloak), Required, gear icon: level
high, matchat-least - Deny access, Required
-
Condition - risk level (Skycloak), Required, gear icon: level
- Then add a Conditional sub-flow Step up medium risk containing:
-
Condition - risk level (Skycloak), Required, gear icon: level
medium, matchexactly - Condition - user configured, Required
- OTP Form (or WebAuthn Authenticator), Required
-
Condition - risk level (Skycloak), Required, gear icon: level
- Bind the new flow as the realm’s Browser flow
browser (copy)
├── Cookie Alternative
└── forms Alternative
├── Username Password Form Required
├── Adaptive Risk - Evaluate (Skycloak) Required
├── Deny high risk Conditional
│ ├── Condition - risk level (high, at-least) Required
│ └── Deny access Required
└── Step up medium risk Conditional
├── Condition - risk level (medium, exactly) Required
├── Condition - user configured Required
└── OTP Form Required
Keep the evaluator inside the forms sub-flow: the extension learns from a login only when the whole flow succeeds, so a login stopped at step-up or denied teaches it nothing. For recent_failures to fire, turn on Brute force detection in Realm settings > Security defenses.
Read the risk in events
Turn on Save events in Realm settings > Events. Every evaluated LOGIN event, and the LOGIN_ERROR of a denied login, carries these details:
| Detail | Example | Meaning |
|---|---|---|
risk_score |
45 |
0 to 100 |
risk_level |
medium |
low, medium or high
|
risk_reasons |
new_device,new_network |
Reasons that fired, learning while the profile is learning, or none
|
risk_country |
CA |
Present when the country header gave one |
The same details reach your webhooks and event exports, so you can alert on risk_level in your SIEM. If scoring ever fails, the login continues as low risk with the reason evaluation_error.
Only browser logins are scored. Logins that run no browser flow, such as the password grant or client credentials, are not stepped up. See the Extensions page for all extension options.
IP-Based MFA
Require MFA from untrusted networks:
// Require MFA outside corporate network
function authenticate(context) {
var clientIP = context.httpRequest.getRemoteAddress();
var trustedNetwork = "192.168.1.0/24";
if (!isInNetwork(clientIP, trustedNetwork)) {
context.challenge("otp");
return;
}
context.success();
}Recovery Options
Backup Codes
- Generate during MFA enrollment
- Store securely
- Single-use only
- Regenerate when depleted
Admin Override
Administrators can:
- Reset user MFA
- Temporarily disable MFA
- Force re-enrollment
User Management
Enforcing MFA
For All Users
- Set MFA execution as Required in browser flow
- Add to default required actions
For Specific Groups
- Create custom authentication flow
- Use conditional authenticator
- Check group membership
For Specific Roles
// Conditional MFA for roles
if (user.hasRole("sensitive-data-access")) {
context.challenge("otp");
}MFA Status Monitoring
View user MFA status:
- Go to Users → Select user
- Check Credentials tab
- View configured MFA methods
Testing MFA
Test Scenarios
-
New User Enrollment
- Register new account
- Complete MFA setup
- Verify login with MFA
-
Existing User Migration
- Enable MFA requirement
- Test forced enrollment
- Verify graceful upgrade
-
Recovery Flow
- Test with backup codes
- Admin reset scenario
- Re-enrollment process
Best Practices
-
Gradual Rollout
- Start with pilot group
- Monitor adoption
- Address issues before full deployment
-
User Communication
- Provide clear instructions
- Offer multiple MFA options
- Support documentation
-
Security Considerations
- Enforce strong MFA methods
- Regular security reviews
- Monitor suspicious patterns
-
Backup Methods
- Always provide recovery options
- Multiple MFA methods
- Admin override procedures
Troubleshooting
Common Issues
TOTP Time Sync Issues
Problem: Invalid code errors Solution:
- Check device time settings
- Increase look-ahead window
- Sync time on user device
WebAuthn Not Working
Problem: Browser doesn’t prompt for security key Solution:
- Check HTTPS requirement
- Verify browser compatibility
- Check Relying Party ID
SMS Delivery Failures
Problem: Users not receiving SMS codes Solution:
- Verify SMS gateway configuration
- Check carrier filtering
- Review delivery logs
Compliance Considerations
Regulatory Requirements
- PCI DSS: MFA for admin access
- HIPAA: MFA for PHI access
- SOC 2: MFA for production systems
- GDPR: Strong authentication for personal data
Audit and Reporting
Monitor MFA usage:
- Enrollment rates
- Authentication success/failure
- Method preferences
- Recovery usage