Chuyển đến nội dung chính

Lesson 11: Multi-Factor Authentication - OTP, WebAuthn and Passkeys

Configure Two-Factor Authentication with TOTP/HOTP (Google Authenticator, FreeOTP), OTP Policy settings, Recovery Codes. WebAuthn setup (FIDO2 security keys), WebAuthn Passwordless Policy. Passkeys integration (conditional and modal UI), Passkeys registration via AIA, Kerberos authentication and X.509 client certificate authentication.

🔒 DevSecOps — Lesson 11 Lesson 11: Multi-Factor Authentication - OTP, WebAuthn and Passkeys

Keycloak from Basic to Advanced

Part 3: Authentication, MFA and Identity Brokering

xdev.asia

1. OTP Authentication — TOTP and HOTP

OTP (One-Time Password) is the most popular MFA method. Keycloak supports two types:

TypeDescriptionAlgorithm
TOTP (Time-based)OTP code changes over time (every 30s)RFC 6238
HOTP (HMAC-based)OTP code changes according to counterRFC 4226

TOTP is recommended for more security — the code expires over time. HOTP is only used when the device does not support accurate clocks.

1.1 OTP Policy Configuration

Configure OTP Policy at Authentication → Policies → OTP Policy:

SettingDescriptionDefault valueRecommended
OTP TypeTOTP or HOTPtotptotp
OTP Hash AlgorithmHash AlgorithmSHA1SHA256 or SHA512
Number of DigitsNumber of OTP digits66 (best compatible)
Look Ahead WindowNumber of acceptable pre/post codes11 — increased if the user is prone to time lag
OTP Token PeriodTime to live per token (seconds) — only TOTP3030
Initial CounterInitial Counter — only HOTP00
Supported ApplicationsApps shown in setup instructionsFreeOTP, Google AuthenticatorAdd more apps if needed

1.2 Setup OTP with Google Authenticator / FreeOTP

Step 1: Turn on OTP in Authentication Flow

By default, Browser Flow has Browser - Conditional OTP sub-flow. OTP is only required when the user has set up OTP credential. To force all users must set up OTP:

  1. Go to Authentication → Required Actions
  2. Find Configure OTP
  3. On Default Action: On — all new users must set up OTP
  4. Or turn on Required in column "Set as default action" to enforce for existing users

Step 2: User registers OTP

When user logs in for the first time (or after admin turns on Required Action):

  1. Keycloak displays page "Mobile Authenticator Setup"
  2. User opens Google Authenticator or FreeOTP
  3. app
  4. Scan QR code or enter manual key
  5. Enter the confirmation OTP code from app
  6. OTP credential saved for user

Step 3: Authenticate OTP

From the next login, after entering username/password, the user must enter the OTP code from the app.

1.3 Manage OTP Credentials

# Admin xem OTP credentials của user
curl -s -X GET \
  "https://keycloak.example.com/admin/realms/myrealm/users/$USER_ID/credentials" \
  -H "Authorization: Bearer $ADMIN_TOKEN" | \
  jq '.[] | select(.type=="otp")'

# Admin xóa OTP credential (force user setup lại)
curl -X DELETE \
  "https://keycloak.example.com/admin/realms/myrealm/users/$USER_ID/credentials/$CREDENTIAL_ID" \
  -H "Authorization: Bearer $ADMIN_TOKEN"

# Admin trigger Required Action cho user cụ thể
curl -X PUT \
  "https://keycloak.example.com/admin/realms/myrealm/users/$USER_ID" \
  -H "Authorization: Bearer $ADMIN_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "requiredActions": ["CONFIGURE_TOTP"]
  }'

2. Recovery Codes

Recovery Codes allow users to restore access when losing their OTP device.

2.1 Enable Recovery Codes

  1. Go to Authentication → Required Actions
  2. Find Recovery Authentication Codes (if not, need to register)
  3. Enable Default Action or Enabled

Enforce Recovery Codes sau OTP setup:

To force users to create recovery codes immediately after setting up OTP, configure Required Actions in the following order:

  1. CONFIGURE_TOTP — Setup OTP first
  2. CONFIGURE_RECOVERY_AUTHN_CODES — Generate recovery codes after

2.2 Using Recovery Codes

When user loses device OTP:

  1. At the OTP input screen, click "Try another way"
  2. Select "Recovery Code"
  3. Enter one of the saved recovery codes
  4. Each code can only be used once
  5. After using all the codes, need to regenerate

3. WebAuthn (FIDO2) — Security Keys

WebAuthn allows authentication using hardware security keys (YubiKey, Google Titan) or platform authenticators (Touch ID, Windows Hello).

3.1 Setup WebAuthn trong Browser Flow

Step 1: Add WebAuthn to Authentication Flow

  1. Duplicate Browser Flow → Browser with WebAuthn
  2. In the Forms sub-flow, add WebAuthn Authenticator
  3. Flow structure:
    Browser with WebAuthn
    ├── Cookie (Alternative)
    └── Forms (Alternative)
        ├── Username Password Form (Required)
        └── WebAuthn MFA (Conditional)
            ├── Condition - User Configured (Required)
            └── WebAuthn Authenticator (Required)
  4. Bind flow: Authentication → Bindings → Browser Flow = Browser with WebAuthn

Step 2: Turn on Required Action

  1. Go to Authentication → Required Actions
  2. Find WebAuthn Register → enable Default Action
  3. New users will be asked to register security key

3.2 WebAuthn Policy Configuration

Configure at Authentication → Policies → WebAuthn Policy:

SettingDescriptionValue
Relying Party Entity NameDisplay name for userKeycloak or company name
Signature AlgorithmsSignature AlgorithmES256 (recommended), RS256
Relying Party IDKeycloak's domainkeycloak.example.com (or leave blank = auto)
Atestation Conveyance PreferenceAttestation request from keynot specified or direct
Authenticator AttachmentAuthenticator typenot specified (accepts both USB key and platform)
Require Resident KeyKey must be stored on devicenot specified
User Verification RequirementRequires user authentication on devicenot specified or required
Create TimeoutTimeout when registering key (seconds)0 (no timeout)
Avoid Same Authenticator RegisterDo not allow registering the same key twiceOff
Acceptable AAGUIDsWhitelist security key modelsLeave blank = accept all

3.3 Managing WebAuthn Credentials

# Xem WebAuthn credentials của user
curl -s -X GET \
  "https://keycloak.example.com/admin/realms/myrealm/users/$USER_ID/credentials" \
  -H "Authorization: Bearer $ADMIN_TOKEN" | \
  jq '.[] | select(.type=="webauthn")'

# Response
{
  "id": "credential-uuid",
  "type": "webauthn",
  "userLabel": "YubiKey 5",
  "createdDate": 1711900000000,
  "credentialData": "{\"aaguid\":\"...\",\"credentialPublicKey\":\"...\"}"
}

Users can also manage security keys themselves in Account Console at /realms/myrealm/account/#/security/webauthn.

4. Passkeys

Passkeys is the next evolution of WebAuthn — allowing passwordless login (passwordless) using fingerprint, face recognition, or device PIN.

4.1 Passkeys vs Traditional WebAuthn

FeaturesWebAuthn (MFA)Passkeys (Passwordless)
PurposeMFA — add step after passwordComplete password replacement
AuthenticatorWebAuthn AuthenticatorWebAuthn Passwordless Authenticator
PolicyWebAuthn PolicyWebAuthn Passwordless Policy
Required ActionWebAuthn RegisterWebAuthn Register Passwordless
Resident KeyOptionalDiscoverable credential
User VerificationOptionalRequired (biometrics/PIN)

4.2 Enable Passkeys

Step 1: Configure WebAuthn Passwordless Policy

  1. Go to Authentication → Policies → WebAuthn Passwordless Policy
  2. Configuration:
    • Relying Party Entity Name: My Company
    • Signature Algorithms: ES256
    • User Verification Requirement: required
    • Require Resident Key: Yes — required for Passkeys

Step 2: Create Passwordless Browser Flow

Passwordless Browser Flow
├── Cookie (Alternative)
└── Passwordless Login (Alternative)
    ├── WebAuthn Passwordless Authenticator (Alternative)  → Đăng nhập bằng Passkey
    └── Username Password Fallback (Alternative)           → Sub-flow fallback
        ├── Username Password Form (Required)
        └── Conditional OTP (Conditional)
            ├── Condition - User Configured (Required)
            └── OTP Form (Required)

Step 3: Turn on Required Action

  1. Go to Authentication → Required Actions
  2. On WebAuthn Register Passwordless → Default Action: On

4.3 Passkey UI Modes — Conditional and Modal

Conditional UI (Autofill):

Passkey is automatically suggested in the username field — users just need to select and authenticate using biometrics. This is the smoothest experience.

To enable Conditional UI, put WebAuthn Passwordless Authenticator first in the flow with requirement Alternative.

Modal UI:

Browser displays a dialog asking for Passkey authentication. User must interact with the dialog. Used when you want to make the UX explicit.

4.4 Register Passkey via AIA (Application Initiated Action)

Users can register for Passkey at any time via AIA link:

# AIA URL để trigger Passkey registration
GET /realms/myrealm/protocol/openid-connect/auth?
  client_id=my-app&
  redirect_uri=https://myapp.example.com/callback&
  response_type=code&
  scope=openid&
  kc_action=webauthn-register-passwordless

Skip if exists: If you want to skip registration when the user already has Passkey:

# Thêm parameter skip_if_exists
GET /realms/myrealm/protocol/openid-connect/auth?
  client_id=my-app&
  redirect_uri=https://myapp.example.com/callback&
  response_type=code&
  scope=openid&
  kc_action=webauthn-register-passwordless&
  kc_action_parameter=skip_if_exists

JavaScript integration:

// Sử dụng keycloak-js adapter
const keycloak = new Keycloak({
  url: 'https://keycloak.example.com',
  realm: 'myrealm',
  clientId: 'my-app'
});

// Trigger Passkey registration
function registerPasskey() {
  keycloak.login({
    action: 'webauthn-register-passwordless'
  });
}

// Button trong UI
document.getElementById('registerPasskeyBtn')
  .addEventListener('click', registerPasskey);

5. Kerberos Authentication

Kerberos enables Single Sign-On automatically for users logged in to the domain (Windows AD, MIT Kerberos). User does not need to enter credentials — browser automatically sends Kerberos ticket.

5.1 Kerberos Server Setup

Request:

  • KDC (Key Distribution Center) is running — Active Directory or MIT Kerberos
  • SPN (Service Principal Number) cho Keycloak service
  • Keytab file cho Keycloak
# Tạo SPN và keytab cho Keycloak (MIT Kerberos)
kadmin -q "addprinc -randkey HTTP/[email protected]"
kadmin -q "ktadd -k /etc/keycloak/keycloak.keytab HTTP/[email protected]"

# Đặt permission
chmod 600 /etc/keycloak/keycloak.keytab
chown keycloak:keycloak /etc/keycloak/keycloak.keytab

5.2 Configure Keycloak for Kerberos

Option 1: Kerberos User Storage Provider

  1. Go to User Federation → Add provider → Kerberos
  2. Configuration:
    • Kerberos Realm: EXAMPLE.COM
    • Server Principal: HTTP/[email protected]
    • Key Tab: /etc/keycloak/keycloak.keytab
    • Allow Kerberos Authentication: On
    • Use Kerberos For Password Authentication: On
    • Update First Login: On

Option 2: LDAP + Kerberos (Active Directory)

  1. Go to User Federation → Add provider → LDAP
  2. Configure LDAP connection for AD
  3. Enable Allow Kerberos Authentication
  4. Enter Kerberos Realm, Server Principal, Key Tab

5.3 Enable Kerberos in Browser Flow

  1. Go to Authentication → Flows → Browser (or custom flow)
  2. Find Kerberos execution
  3. Change requirement from Disabled to Alternative
Browser Flow (Kerberos enabled)
├── Cookie (Alternative)
├── Kerberos (Alternative)                    ← BẬT lên
├── Identity Provider Redirector (Alternative)
└── Forms (Alternative)
    ├── Username Password Form (Required)
    └── Browser - Conditional OTP (Conditional)
        ├── Condition - User Configured (Required)
        └── OTP Form (Required)

5.4 Cross-realm Trust

Allow users from one Kerberos realm to trust another realm:

# /etc/krb5.conf trên Keycloak server
[libdefaults]
    default_realm = CORP.EXAMPLE.COM
    dns_lookup_realm = false
    dns_lookup_kdc = false

[realms]
    CORP.EXAMPLE.COM = {
        kdc = dc1.corp.example.com
        admin_server = dc1.corp.example.com
    }
    PARTNER.EXAMPLE.COM = {
        kdc = kdc.partner.example.com
    }

[capaths]
    PARTNER.EXAMPLE.COM = {
        CORP.EXAMPLE.COM = .
    }

6. X.509 Client Certificate Authentication

X.509 allows authentication using client certificate — common in enterprise, government, or mTLS environments.

6.1 Add X.509 to Browser Flow

  1. Duplicate Browser Flow → Browser with X.509
  2. Add X509/Validate Username Form to flow
Browser with X.509
├── Cookie (Alternative)
├── X509/Validate Username Form (Alternative)  ← Thêm mới
├── Identity Provider Redirector (Alternative)
└── Forms (Alternative)
    ├── Username Password Form (Required)
    └── Browser - Conditional OTP (Conditional)
        ├── Condition - User Configured (Required)
        └── OTP Form (Required)

6.2 X.509 Authenticator Configuration

Click ⚙️ next to X509/Validate Username Form:

SettingDescriptionExample
User Identity SourceField in certificate to identify userSubject's Common Name, Subject's e-mail
Mapping Source to User AttributeMap identity source sang user attributeUsername or Email
A regular expressionRegex extract identity from cert fieldCN=(.*?)(?:,\|$)
CRL Checking EnabledChecking Certificate Revocation ListOn
CRL Distribution PointURL or path to CRLldap://ca.example.com/CN=...
OCSP Checking EnabledChecking Online Certificate Status ProtocolOn
OCSP Responder URIOCSP responder URLhttp://ocsp.example.com
Certificate Key UsageKey usage requireddigitalSignature
Certificate Extended Key UsageExtended key usageclientAuth
Certificate Policy Validation ModeValidate certificate policiesNot Specified

6.3 Certificate Mapping Strategies

There are many ways to map certificate to Keycloak user:

# 1. Subject's Common Name → Username
# Certificate: CN=john.doe, OU=Engineering, O=Example Corp
# → username: john.doe

# 2. Subject's e-mail → Email
# Certificate: [email protected]
# → email: [email protected]

# 3. Serial Number → User Attribute
# Certificate: Serial=1A2B3C4D
# → user attribute "x509_serial" = "1A2B3C4D"

# 4. SHA-256 Certificate Thumbprint → User Attribute
# Certificate SHA-256: ab:cd:ef:12:34:...
# → user attribute "x509_thumbprint" = "ab:cd:ef:12:34:..."

# 5. Subject's DN với regex
# Certificate: CN=john.doe, OU=Engineering, O=Example Corp, C=VN
# Regex: CN=(.*?)(?:,|$)
# → Extracted: john.doe

6.4 CRL and OCSP Checking

To ensure certificates have not been revoked:

CRL (Certificate Revocation List):

  • Keycloak downloads and caches CRL from configured URI
  • Check if the serial number of the client cert is in the CRL
  • If CRL not available and CRL Checking Enabled=On → authentication fail

OCSP (Online Certificate Status Protocol):

  • Real-time check certificate status
  • Keycloak sends request to OCSP Responder
  • Faster than CRL for individual checks
  • Disadvantages: depends on OCSP server availability
# Test OCSP check
openssl ocsp \
  -issuer ca.pem \
  -cert client.pem \
  -url http://ocsp.example.com \
  -resp_text

# Test certificate info
openssl x509 -in client.pem -noout -subject -serial -fingerprint -sha256

6.5 Keycloak mTLS Setup

For Keycloak to receive client certificates, you need to configure a reverse proxy or Keycloak directly:

# Docker Compose — Keycloak với mTLS qua nginx
services:
  nginx:
    image: nginx:latest
    ports:
      - "443:443"
    volumes:
      - ./nginx.conf:/etc/nginx/conf.d/default.conf
      - ./certs/server.crt:/etc/nginx/certs/server.crt
      - ./certs/server.key:/etc/nginx/certs/server.key
      - ./certs/ca.crt:/etc/nginx/certs/ca.crt
    depends_on:
      - keycloak

  keycloak:
    image: quay.io/keycloak/keycloak:26.0
    command: start --proxy-headers xforwarded
    environment:
      KC_HOSTNAME: keycloak.example.com
      KC_HTTP_ENABLED: "true"
      KC_PROXY_HEADERS: xforwarded
      KEYCLOAK_ADMIN: admin
      KEYCLOAK_ADMIN_PASSWORD: admin
# nginx.conf — Forward client certificate
server {
    listen 443 ssl;
    server_name keycloak.example.com;

    ssl_certificate /etc/nginx/certs/server.crt;
    ssl_certificate_key /etc/nginx/certs/server.key;
    ssl_client_certificate /etc/nginx/certs/ca.crt;
    ssl_verify_client optional;

    location / {
        proxy_pass http://keycloak:8080;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-Forwarded-Port 443;
        
        # Forward client certificate
        proxy_set_header ssl-client-cert $ssl_client_escaped_cert;
    }
}

7. Compare MFA

methods
MethodSecurityUXPhishing-resistantUse case
TOTP/HOTPAverageGoodNoPopular, easy to deploy
WebAuthn (MFA)HighGoodYesEnterprise, compliance
PasskeysVery HighExcellentYesConsumer + Enterprise
KerberosHighExcellent (transparent)Yes (domain)Enterprise, Windows domain
X.509 CertificateVery HighTransparentYesGovernment, military, banking

8. Summary

ConceptDescription
OTP PolicyTOTP/HOTP config — algorithm, digits, period, look ahead
Recovery CodesBackup codes when OTP device is lost
WebAuthnFIDO2 security keys — phishing-resistant MFA
PasskeysPasswordless authentication — discoverable credentials + biometrics
KerberosTransparent SSO cho domain users
X.509Client certificate authentication — mTLS
CRL/OCSPCertificate revocation checking