FIDO2 and WebAuthn for Enterprise Authentication: Architecture, Deployment, and Hardening
Prerequisites
- Working knowledge of SSO, OIDC, and MFA
- Access to an enterprise IdP or Keycloak admin environment
Steps
FIDO2 and WebAuthn enable phishing-resistant authentication using platform biometrics and security keys instead of shared secrets. This guide explains enterprise architecture patterns, implementation steps, production-ready configuration, and operational hardening for IAM teams.
Overview
FIDO2 is a set of standards from the FIDO Alliance that combines WebAuthn on the browser and application side with CTAP2 between clients and authenticators. Its core purpose is to replace or strengthen passwords with public key cryptography, where the private key stays on a device such as a YubiKey, TPM-backed laptop, or mobile secure enclave.
Enterprises adopt FIDO2 because it is phishing-resistant, reduces credential theft, and improves user experience with biometric or PIN-based sign-in. It is especially effective for workforce SSO, privileged admin access, VPN, VDI, and step-up authentication in Zero Trust programs.
Architecture
Core components:
- Relying Party (RP): the application or IdP that requests registration and authentication
- Browser/WebAuthn client: Chrome, Edge, Firefox, Safari
- Authenticator: roaming key such as YubiKey or platform authenticator such as Windows Hello, Touch ID, Android passkey
- Identity Provider: Microsoft Entra ID, Okta, Ping Identity, Keycloak
- Directory and policy layer: AD, LDAP, SCIM, conditional access, device compliance
Common deployment models:
- IdP-centric: WebAuthn configured at the SSO provider, then inherited by SaaS apps
- Application-native: custom applications act as the RP and store public keys directly
- Hybrid: IdP for workforce SSO, native WebAuthn for high-risk internal apps
View as text
[User Device] | v [Browser] ---- 1. GET /login ----> [Application] [Application] ---- 2. redirect ----> [IdP] [IdP] ---- 3. challenge options ----> [Browser] [Browser] ---- 4. CTAP2 request ----> [Authenticator] [Authenticator] ---- 5. signed assertion ----> [Browser] [Browser] ---- 6. credential response ----> [IdP] [IdP] ---- 7. token/SAML/OIDC ----> [Application] [Application] ---- 8. session established ----> [User Device]
Data flow summary:
- RP creates a registration or authentication challenge.
- Browser invokes WebAuthn APIs.
- Authenticator signs the challenge with a device-bound private key.
- RP or IdP validates the signature using the stored public key.
- Access is granted without transmitting a password.
Implementation Guide
Example: enable WebAuthn on Keycloak for enterprise SSO.
- Start Keycloak and create an admin user.
podman run -d --name keycloak -p 8080:8080 -e KEYCLOAK_ADMIN=admin -e KEYCLOAK_ADMIN_PASSWORD='Str0ngPass!' quay.io/keycloak/keycloak:25.0 start-dev
- Create a realm and enable WebAuthn policy.
kcadm.sh config credentials --server http://localhost:8080 --realm master --user admin --password 'Str0ngPass!'
kcadm.sh create realms -s realm=corp -s enabled=true
kcadm.sh update authentication/required-actions/WEBAUTHN_REGISTER --realm corp -s enabled=true -s defaultAction=true
- Configure WebAuthn parameters.
kcadm.sh update realms/corp --realm corp -f webauthn-policy.json
- Create an OIDC client.
kcadm.sh create clients -r corp -s clientId=portal-app -s enabled=true -s publicClient=false -s 'redirectUris=["https://portal.example.com/*"]' -s protocol=openid-connect
- Enforce MFA or passwordless in browser flow from the admin console or via exported realm JSON.
- Register a hardware key or platform authenticator during first login.
webauthn-policy.json:
{
"webAuthnPolicyRpEntityName": "Example Corp",
"webAuthnPolicySignatureAlgorithms": ["ES256", "RS256"],
"webAuthnPolicyRpId": "login.example.com",
"webAuthnPolicyAttestationConveyancePreference": "not specified",
"webAuthnPolicyAuthenticatorAttachment": "not specified",
"webAuthnPolicyRequireResidentKey": "No",
"webAuthnPolicyUserVerificationRequirement": "required",
"webAuthnPolicyCreateTimeout": 60000,
"webAuthnPolicyAvoidSameAuthenticatorRegister": true
}
Code Examples
1. Validate RP endpoint with curl
curl -sk https://login.example.com/realms/corp/.well-known/openid-configuration | jq '.issuer,.authorization_endpoint'
2. Kubernetes ingress enforcing HTTPS and HSTS
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: keycloak
annotations:
nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
nginx.ingress.kubernetes.io/configuration-snippet: |
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
spec:
tls:
- hosts:
- login.example.com
secretName: login-example-com-tls
3. Python verification of origin and RP ID before processing WebAuthn response
expected_origin = "https://login.example.com"
expected_rp_id = "login.example.com"
client_data = response["clientDataJSON"]
assert client_data["origin"] == expected_origin
assert credential["rpId"] == expected_rp_id
assert response["type"] == "public-key"
Security Hardening
- Require user verification for workforce and admin use cases.
- Restrict RP ID to the exact production domain; avoid wildcard thinking.
- Enforce TLS 1.2+, HSTS, secure cookies, and origin validation.
- Prefer attestation policies only where supply-chain assurance matters; otherwise avoid privacy-heavy collection.
- Maintain break-glass accounts in a PAM vault with strong monitoring.
- Combine FIDO2 with device compliance and conditional access for unmanaged endpoints.
- Log registration, challenge issuance, sign-in success, sign-in failure, and authenticator lifecycle events to SIEM.
Comparison
| Capability | FIDO2/WebAuthn | Cisco Duo | RSA SecurID |
|---|---|---|---|
| Pricing | Often bundled in IdP/browser ecosystem; hardware key cost extra | Per-user subscription | Enterprise licensing, often higher for legacy estates |
| Deployment | Native in modern browsers and OS platforms | Cloud-managed, easy MFA rollout | Strong in legacy and regulated environments |
| Scalability | High, stateless public-key model | High for SaaS-centric deployments | Good, but legacy integration can add complexity |
| Security | Phishing-resistant, origin-bound cryptography | Strong MFA, push fatigue risk unless using Duo Passwordless | Strong MFA, less web-native than WebAuthn |
Troubleshooting
- Origin mismatch
2026-10-14 09:12:44,118 WARN [org.keycloak.authentication] (executor-thread-7) WebAuthn authentication failed: Invalid origin, expected https://login.example.com, got https://portal.example.com
Fix: ensure the browser calls the RP on the exact configured origin and that reverse proxies preserve Host and X-Forwarded-Proto.
- RP ID validation failure
2026-10-14 09:14:02,771 ERROR [org.keycloak.services] (executor-thread-9) WEBAUTHN_ERROR: rpIdHash mismatch for credentialId=AZERTY1234
Fix: set webAuthnPolicyRpId to the effective login domain, not an internal hostname.
- Authenticator not allowed by policy
2026-10-14 09:15:19,553 WARN [org.keycloak.events] type=WEBAUTHN_REGISTER_ERROR, realmId=corp, clientId=account-console, error=invalid_registration, reason="user verification required but authenticator returned discouraged"
Fix: use an authenticator supporting UV, or relax policy only for low-risk populations.
Best Practices
Do
- Use discoverable credentials/passkeys for better UX on managed devices.
- Pilot with admins and help desk first, then expand to all users.
- Register at least two authenticators per user, for example Windows Hello and a YubiKey.
- Monitor metrics such as registration completion, sign-in failure rate, and fallback usage.
Don't
- Do not leave SMS OTP as the primary fallback for privileged users.
- Do not set RP ID to a temporary domain during testing and forget to change it before production.
- Do not disable origin checks behind proxies; fix the proxy headers instead.
Have a project in mind?
Get an instant AI price estimate for it, or talk directly to our team.
One email a month on what we learn building with AI