Short-lived MFA challenges bind to a pending session, step-up gates sensitive actions, recovery codes are single-use, and a password replay cannot skip an in-progress or required second factor.
Package status: reference context ready for human review. The contract and test scenarios are complete, but no claim is made that an adopting implementation has passed them.
Decision
After the first factor succeeds, the browser holds only a pending session that cannot call authenticated APIs. A short-lived challenge record binds that pending session to a method such as TOTP. Completing the challenge promotes the pending session, using related session-lifecycle rotation, to an aal2 session. Sensitive actions in the profile require a fresh step-up challenge even if aal2 was granted earlier. Recovery codes are stored hashed, used once, and entered on a separate control so they are not mixed with TOTP digits. Re-submitting the password while a challenge is pending does not skip MFA.
Scope
- Challenge issuance, binding, expiry, attempt budget, step-up, recovery-code consumption, and UX states for browser-applications.
- Promotion of pending sessions into durable sessions via the related session package.
Outside this block
- Session cookie format beyond the promotion trigger, TOTP enrollment, WebAuthn ceremonies, and SMS OTP (not in this profile).
Contract
- A challenge id is required to complete MFA and is bound to one pending session; it is useless on another session.
- Challenge ttl and max attempts are enforced server-side; the UI cannot extend them.
- Step-up actions in the profile do not commit until a new challenge succeeds and session auth level is aal2.
- Recovery codes consume their digest on first success and cannot be reused.
- Password replay against login during a pending challenge does not issue a full session.
- The pending screen keeps keyboard focus in the code field and announces lockout without revealing whether the TOTP secret exists.
Implementation guidance
- Store pending sessions separately from active sessions so API middleware can reject them. Hash recovery codes with a unique salt and show remaining count after success.
- On step-up, snapshot the intended action id so a completed challenge cannot be applied to a different payout change.
Failure handling and safeguards
- Exhausted attempts expire the challenge and require a new first factor. If promotion fails after a valid TOTP, do not consume a recovery code.
- Lost-device support must not become a password-only bypass.
Verification and operations
- UX tests complete step-up on payout change and prove the mutation timestamp is after aal2. Reuse a recovery code and expect failure. Replay password during pending and expect still-pending.
The executable-looking examples in this package are fixtures and acceptance contracts. Run
python tools/validate.py from the collection root to check package structure and metadata; then
implement and execute the scenarios in the target repository.
Adoption assumptions
- Names and numeric values in
example.yamlare an adoption profile, not universal defaults. - The adopting team must map actors, data classes, error vocabulary, and ownership to its system.