Skip to main content

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_admins and auth.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:

  1. removes the user's OTP credential(s) in Keycloak;
  2. queues CONFIGURE_TOTP so the user re-enrolls on the MOD page at next sign-in;
  3. writes an append-only auth.mfa_reset security-audit row (actor, target, reason, credential IDs — metadata only, never a secret), which the SIEM export ships like every other security event;
  4. 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_principals group;
  • service-account-* users;
  • *@service.local;
  • api_service and platform-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​

  1. Sign in as a normal user, open Profile → Security, and confirm the status line and the Set up 2FA button.
  2. Complete setup on the MOD-themed page and confirm the Dashboard toast.
  3. 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.
  4. Attempt a reset on api@service.local and confirm the 409 refusal.
  5. Confirm no MOD page links to /realms/*/account.