2FA model (design principles)
Flowxi implements 2FA with the following guarantees:- No 2FA secret is ever stored without proof
- Enrollment is explicit and atomic
- No automatic logout on enable or disable
- Deterministic behavior across login, verification, and management
- Full localization of all responses
Enrollment lifecycle (critical)
2FA enrollment follows a two-phase model.Phase 1: Pending enrollment (cache only)
- A secret is generated on demand
- Stored only in cache
- Has a strict TTL (
auth.twofa_enroll_ttl) - Never written to the database
Phase 2: Commit after verification
- The user proves ownership by submitting a valid TOTP code
- Only then:
- the secret is stored in DB
twofa_enabledis set totrue
- The pending secret is immediately cleared from cache
- ghost 2FA states
- unused secrets in DB
- accidental activation
Get 2FA status
Endpoint
GET /api/v1/auth/2fa/status(Protected — Bearer token required) This endpoint is the single source of truth for frontend state.
Response when 2FA is enabled
If the user already has 2FA enabled:enabled: true- no secret
- no otpauth URI
- frontend must assume 2FA is active
- re-enrollment is not possible without disabling first
Response when 2FA is disabled
If 2FA is not enabled:- a pending secret is created (or reused if still valid)
- the response includes:
secretotpauth_uriexpires_inissuer
- show a QR code
- display the manual secret
- show a countdown for enrollment expiration
Enable 2FA
Endpoint
POST /api/v1/auth/2fa/enable(Protected — Bearer token required)
Input
code(string) — TOTP code from the authenticator app
Validation & rules
When enabling 2FA, Flowxi enforces:- rate limiting per
user + ip - verification against the pending secret
- TOTP tolerance of ±1 interval
- immediate failure if the pending secret is missing or expired
On success
If the code is valid:- the secret is written to the database
twofa_enabledis set totrue- the pending secret is removed from cache
- existing sessions remain active
On failure
Possible failure cases include:- missing pending secret
- invalid or expired code
- too many attempts
Disable 2FA
Endpoint
POST /api/v1/auth/2fa/disable(Protected — Bearer token required)
Input
code(string) — TOTP code generated using the current DB secret
Validation & rules
Disabling 2FA enforces:- rate limiting per
user + ip - verification against the stored DB secret
- strict validation of the TOTP code
On success
If the code is valid:twofa_enabledis set tofalsetwofa_secretis cleared from DB- any pending enrollment secret is cleared
- future enrollment requires a new secret
Step-up verification (sensitive actions)
Endpoint
POST /api/v1/auth/2fa/verify(Protected — Bearer token required) This endpoint is used to prove 2FA before performing sensitive operations (e.g. security changes, high-risk actions).
Behavior
- verifies a TOTP code against the DB secret
- does not issue a token
- updates
twofa_last_verified_atif the column exists - rate-limited per
user + ip
2FA during login
When a user has 2FA enabled, login becomes a two-step flow.Step 1: Login
POST /api/v1/auth/login
Instead of issuing a token, the API returns:
mfa_required: truechallenge_idotp_type: totpexpires_in
- stored in cache
- bound to IP + User-Agent + device_id
- limited to 5 attempts
- hard-expiring (TTL never extended)
Step 2: Verify login challenge
POST /api/v1/auth/2fa/verify-login
- validates the challenge bindings
- verifies the TOTP code
- consumes the challenge
- issues a token only after success
Security constraints (summary)
Flowxi enforces the following for 2FA:- rate-limited enable, disable, verify, and login verification
- no secret persistence without proof
- no QR/secret leakage once enabled
- strict challenge binding (IP + User-Agent)
- deterministic error codes
- full localization of all responses
Frontend integration rules
- Always call
/auth/2fa/statusto determine UI state - Never assume 2FA state locally
- Do not store secrets or challenges long-term
- Use
codefields for logic, not messages - Treat 2FA as a mandatory second step when required
Guarantees
Flowxi 2FA guarantees:- no partial enrollment states
- no silent activation
- no forced session invalidation
- no ambiguous login outcomes
- consistent, auditable behavior across all flows

