MFA enforcement
z4j can require users to enroll in TOTP MFA. Enforcement is opt-in and off by default: with the flags at their defaults, nothing changes for anyone, on any upgrade. Flipping a flag starts a per-user grace window; a targeted user who has not enrolled by the deadline keeps their password login but loses everything else until they enroll.
The knobs
Section titled “The knobs”| Variable | Default | Description |
|---|---|---|
Z4J_MFA_ENFORCE_FOR_ADMINS |
false |
Require enrollment for every user with the global is_admin bit. |
Z4J_MFA_ENFORCE_FOR_ALL |
false |
Require enrollment for every user. Stricter superset of the admins-only flag. |
Z4J_MFA_ENROLLMENT_GRACE_DAYS |
7 |
Days a targeted user has to enroll once their grace clock starts. Range 0..90; 0 restricts targeted users from their first post-policy login onward. |
Z4J_MFA_ENFORCE_FOR_ADMINS targets the global admin bit only. Project-level admin memberships do not count: project roles are per-project authorization a user can gain and lose many times a day, and anchoring an account-level grace clock to them would make the deadline flap. Enforcement is global per brain instance; there is no per-project policy.
A targeted user who already has MFA enrolled is unaffected. The policy only has something to demand from targeted users without MFA.
Grace-window semantics
Section titled “Grace-window semantics”The grace clock does not start when you flip the flag. It starts per user, at the first login that observes the policy applying to that user, when the brain stamps a one-time anchor (users.mfa_enforcement_started_at). The deadline is anchor + Z4J_MFA_ENROLLMENT_GRACE_DAYS.
Consequences of that design:
- Existing accounts get the full window. Enabling enforcement on a long-running install never instantly locks anyone out; each user’s clock starts at their next sign-in.
- A user who never signs in never starts their clock, and is never blocked. (They also hold no session, so there is nothing to restrict.)
- The anchor is one-way. It is stamped once and never cleared, so a user cannot reset their grace window by enrolling, disabling MFA, and waiting for a fresh clock. A user who disables MFA after their original deadline has passed is restricted again immediately at their next request.
- Concurrent first logins race safely: the database arbitrates, exactly one login stamps the anchor.
Within the window the user can sign in and work normally; the login response and MFA status endpoint carry the deadline so a client can surface an “enroll by” banner.
What a blocked user experiences
Section titled “What a blocked user experiences”Login past the deadline still succeeds. Refusing the password outright would make enrollment impossible, because enrollment itself requires an authenticated session. Instead, the session is restricted to an enrollment-only allowlist:
GET /api/v1/auth/mePOST /api/v1/auth/logoutGET /api/v1/auth/mfa/statusPOST /api/v1/auth/mfa/enroll-startPOST /api/v1/auth/mfa/enroll-completeEvery other authenticated call answers 403 with the stable error code mfa_enrollment_required and the deadline in the error details. In practice: the user signs in, goes to Settings, Account, Security, completes TOTP enrollment, and the restriction lifts on their very next request. Other dashboard panels will fail to load data until then.
The gate is evaluated on every cookie-session request, so the policy is live in both directions: it engages mid-session the moment a deadline passes, and it lifts immediately after enrollment (or after the operator relaxes the policy), with no re-login needed.
Deadline surfacing
Section titled “Deadline surfacing”Two responses carry the enforcement state so clients can render it:
POST /api/v1/auth/loginreturnsmfa_enrollment_required(bool) andmfa_enrollment_deadline(timestamp or null) alongside the normal login payload.GET /api/v1/auth/mfa/statusreturnsenrollment_requiredandenrollment_deadline. This endpoint stays reachable even for a blocked session, so an enrollment page can render the countdown or the lockout notice.
A null deadline with required: true means the policy targets the user but their grace clock has not started yet (they have not logged in under the policy).
API keys are exempt
Section titled “API keys are exempt”Bearer (API-key) callers are deliberately not evaluated by this policy. API keys are a separate credential class with explicit scopes and no second-factor ceremony; blocking them would break automation the moment an operator’s grace window expired, without improving the security of the interactive account.
The exemption does not leak: minting a new API key is a session-gated action, so a blocked user cannot escape the restriction by creating a key. Keys minted before the deadline keep working, which is the intended behavior for service integrations.
Audit trail
Section titled “Audit trail”Enforcement writes two HMAC-chained audit actions:
| Action | When |
|---|---|
auth.mfa_enforcement_grace_started |
Exactly once per user, at the login that stamps the grace anchor. Metadata records which policy flag applied, the grace days, and the computed deadline - the “when was this account first told” answer a compliance reviewer asks for. |
auth.mfa_enforcement_blocked |
On each login that lands past the deadline. |
Per-request 403s are intentionally not audited; a blocked user’s browser retrying a dashboard poll would otherwise flood the chained log.
Rollout pattern
Section titled “Rollout pattern”- Announce, then set
Z4J_MFA_ENFORCE_FOR_ADMINS=trueand restart the brain. Admins see the deadline at next login. - Watch for
auth.mfa_enforcement_grace_startedrows to confirm the policy is reaching the right accounts. - Once admins are enrolled, escalate to
Z4J_MFA_ENFORCE_FOR_ALL=truewith a fresh announcement.
If a user loses their phone and recovery codes after enrolling, the z4j reset-mfa CLI (lost-phone recovery) clears their MFA state. Under an active enforcement policy the reset does not grant a new grace window (the anchor is one-way), so a reset user past their deadline signs in straight into the enrollment-only session and re-enrolls, which is exactly the flow they need.
See also
Section titled “See also”- Multi-factor authentication for enrollment, recovery codes, trusted devices, and the threat model.
- Environment variables for the full MFA knob list.
- Audit log for how the chained audit rows work.