0020. MFA, step-up, WebAuthn, and passwordless
Context
Multi-factor and passwordless authentication are security table stakes against Entra/Okta/Auth0, and the most security-sensitive place to get the constitution wrong. The factors themselves are unremarkable — a second method in the login ceremony. The hard part is how factor satisfaction reaches the point of enforcement without a core read on the hot path. "This operation requires MFA" must be enforceable at the edge, but whether a principal did MFA is, naively, a fact about a past login — exactly the kind of stateful lookup the constitution forbids on the verification path (ADR-0001 rule 1). This ADR fixes the MFA and passwordless architecture so enforcement stays edge-stateless.
Decision
-
Factors are composable
AuthenticationMethods (ADR-0015), combined by a per-realm authentication policy (ADR-0012). v1-reserved-with-seats: WebAuthn / passkeys (viawebauthn-rs, named in ADR-0002), TOTP (a v1 method already), and passwordless flows (passkey-only, magic link). Passwordless is "drop the password factor," not a separate subsystem. Three WebAuthn specifics are pinned now because they touch other seams: the RP ID is the realm's issuer host (ADR-0012) — a realm that later adds a custom domain (proposal 0007) changes its origin, so the custom-domain decision must land before passkeys are registered against it or credentials bind to the wrong RP ID; challenge state lives in theSessionStore(ADR-0016) — ceremony state is region-local issuance-side state, never core state; and the signature counter is best-effort — recorded and alarmed on regression as a clone signal (ADR-0031), but not a strong per-login core write, which would put a strongly-consistent write on the login path that ADR-0012's write-rate ceiling cannot afford (passkey sync ecosystems have already made counters unreliable as a hard check). -
Assurance is stamped into the token, enforced at the edge. The factors satisfied and the assurance level reached are recorded as the OIDC
amr/acrclaims in the issued token (ADR-0015). A relying party — or Vör, for a Vord-protected resource — enforces an MFA requirement by reading the signed token'samr/acr, never by a core query about the user's recent authentication. Assurance is a property of the artifact, so the rule-1 hot path stays pure. -
Step-up is a re-authentication at Modgud, not an enforcement read at Vör. When an operation needs higher assurance than the current token carries (OIDC
acr_values/max_age), the relying party sends the principal back to Modgud (ADR-0009) for an additional factor; Modgud mints a new token with the higheracr. The ceremony is stateful and at the admission plane; enforcement is the edge reading the new token. The session (ADR-0016) carries assurance so step-up need not re-run satisfied factors. -
The
amr/acrclaim shape is a reserved seat in the token format from v1 (ADR-0003), even though MFA is built later — so that no token-format break is needed when it ships, and so that the v1 issuer already emits assurance claims that future verifiers can rely on.
Consequences
- The constitution holds at its second-most-tempting point (after ADR-0015): an "MFA-required operation verified at the edge with the core unavailable" is just an extension of the ADR-0010 property test, and it passes, because the proof of MFA is in the token, not the core.
- Vord gets passkeys, TOTP, and step-up — modern, phishing-resistant authentication — with the same edge-stateless enforcement as everything else; the security feature does not cost a scaling property.
- WebAuthn registration and assertion are issuance-side ceremonies at Modgud; the passkey public key lives in the directory (ADR-0011) and is read only at login, never at verification (rule 2).
- Reserving the
amr/acrseat now is the small v1 cost that keeps MFA, step-up, and FAPI-grade assurance (ADR-0025) additive — the same reserved-seat discipline ADR-0003 applies to the token format generally.