When the server cannot keep a TOTP secret safely, every route here answers
501 two_factor_unavailable and GET /v1/session reports
two_factor_available: false — read that flag before showing the section, rather
than hitting the 501 mid-flow.Turning it on
Two calls, and the order is the point.1
Start the setup
qr_data_uri for scanning and show secret for anyone who cannot scan.
This does not turn two-factor on — it only stores the secret, encrypted.2
Enable it with a code
409 already_enabled.
Calling enable before setup answers 409 setup_required.
Signing in with it
Sign-in becomes two steps.1
Password
200 — no session yet
two_factor_required. The challenge is valid for 5 minutes, and it
is not a session — it carries its own prefix precisely so it can never be used as
one.2
Code
401 invalid_code and does not consume the challenge: fat
fingers on a 6-digit code are the common case. It does spend one of the
challenge’s ten attempts, and a challenge that runs out answers
invalid_challenge from then on. The rate limit counts per address and an
attacker spreads guesses across addresses, so the budget carried by the
challenge itself is what actually bounds guessing. A correct code consumes the
challenge outright; it dies with the login it authorized.
Recovery codes
Either a TOTP code or a recovery code satisfies thecode field. A recovery code
is consumed on use, and using one sends a notice to the account’s email — that
notice is how someone finds out a code they never used was used.
Regenerating replaces the whole set; the old ones stop working:
Turning it off
403 invalid_credentials.
Disabling deletes the recovery codes with the secret, and mails the account.
Notifications
Four moments send an email, and each exists so an action the owner did not take is visible:- two-factor enabled;
- two-factor disabled;
- a recovery code used to sign in;
- the password changed or reset.