Two-Factor Authentication (2FA)
MOD supports TOTP two-factor authentication and supports security keys (WebAuthn) as a phishing-resistant second factor. Keycloak stores and verifies both — MOD does not implement its own authenticator — but every 2FA action a person takes happens on MOD-branded pages. MOD never sends a user to the Keycloak account console, and no MOD page links to it.
This page describes a capability MOD enables. It does not make a customer aligned with any framework, and enabling 2FA here does not by itself satisfy a control: enrollment coverage, device custody, and the operator's own policy remain the operator's responsibility.
What end users see
Profile → Security shows:
- whether 2FA is on or off, the device label, and the set-up date;
- whether 2FA is required for that account (from the platform settings
auth.mfa_required_for_adminsandauth.mfa_required_for_all); - Set up 2FA and Replace authenticator buttons.
Both buttons start a Keycloak application-initiated action
(kc_action=CONFIGURE_TOTP) on the MOD-themed sign-in page
(login-config-totp.ftl in CORE/startup/keycloak-themes/modtech), which
returns to the Dashboard. The Dashboard surfaces a success or failure toast from
the kc_action_status return parameter, so a cancelled or failed step is visible
rather than silently bouncing the user to the login form.
"Replace authenticator" is the same action: the themed page shows the existing device and asks for a new one, so the old device is replaced without a console visit.
Status is read through GET /v1/me/mfa, which returns enrollment state, the
credential created date, the device label, required, exempt, and the
setup_url. No secret is ever returned.
Security keys (WebAuthn)
Profile → Security also shows a Security keys card: the registered
keys (name and added date only) and an Add security key button. The
button starts the kc_action=WEBAUTHN_REGISTER application-initiated action
on the MOD-themed sign-in page (webauthn-register.ftl in the modtech
theme); at login, the MOD-themed webauthn-authenticate.ftl page asks for
the key. A security key only works on the real MOD sign-in page, which is
what makes it phishing-resistant.
Two platform settings control it:
auth.webauthn_enabled(default ON) — offer the security key next to the authenticator app;auth.phishing_resistant_only(default OFF) — remove the OTP alternative from the browser login flow for human users, so sign-in requires a security key.
Lock-out rail: enabling phishing-resistant-only is refused (409) unless
the acting admin already has a registered key — the first toggle can never
lock every human sign-in out. The change is written through the audited
platform-settings path, and the gov/DoD compliance profiles (CMMC,
FedRAMP-aligned Moderate, FedRAMP-aligned High/IL5) lock the setting ON. Service and
machine accounts (client-credentials, direct grant, e2e_service_principals)
never use the browser flow, so they are unaffected either way.
Forced enrollment
When auth.mfa_required_for_admins (default ON) or auth.mfa_required_for_all
(default OFF) is on, the required action is queued on the in-scope users and the
realm's stock Conditional-OTP step walks them through enrollment on the MOD-themed
page immediately after the password.
Admin reset
Admin → Users has a per-user Reset 2FA action
(POST /v1/admin/users/{user_id}/mfa/reset). It:
- removes the user's OTP credential(s) in Keycloak;
- queues
CONFIGURE_TOTPso the user re-enrolls on the MOD page at next sign-in; - writes an append-only
auth.mfa_resetsecurity-audit row (actor, target, reason, credential IDs — metadata only, never a secret), which the SIEM export ships like every other security event; - notifies the active platform admins in-app.
Authorization follows the existing admin-user convention: a platform admin may target any tenant, a tenant admin only their own tenant and only with the user capability. A cross-tenant target for a non-platform admin is a plain 404. No database row is hard-deleted (ISO 27001).
The audit-action enum label is added by
CORE/API/migrations/20261004g_gy_c4_mfa_reset_audit.py (dry-run default,
--commit applies).
Accounts that are never enrolled
Machine principals can never complete an enrollment, and a pending required action breaks their client-credentials / direct-grant login. These accounts are always exempt, and the reset endpoint refuses them with 409:
- members of the
e2e_service_principalsgroup; service-account-*users;*@service.local;api_serviceandplatform-service.
The same list is enforced in CORE/startup/keycloak-init.sh (GSEC R-010) and in
CORE/API/services/mfa_service.py, and a regression test asserts the two agree.
Verify it
- Sign in as a normal user, open Profile → Security, and confirm the status line and the Set up 2FA button.
- Complete setup on the MOD-themed page and confirm the Dashboard toast.
- As a platform admin, use Admin → Users → Reset 2FA on a test user and confirm
the audit row (
auth.mfa_reset) and the admin notification. - Attempt a reset on
api@service.localand confirm the 409 refusal. - Confirm no MOD page links to
/realms/*/account.