Skip to main content
Two-factor applies to staff accounts — the people who sign into the panel. It is TOTP: an authenticator app, a 6-digit code, plus 8 single-use recovery codes.
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

Render 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

Splitting it in two is what keeps an abandoned setup from locking anyone out: the secret exists but two-factor is off until the user proves they can produce a code from it.
recovery_codes is shown once and only here. Only hashes are stored. Tell the user to save them somewhere that is not the phone holding the authenticator.
Calling setup while two-factor is already on answers 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
Branch on 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

Answers a normal session — the same body as a login without two-factor.
A wrong code answers 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 the code 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:
Never partial. A partially-known set is worse than a fresh one.

Turning it off

The current password is required. A stolen session must not be enough to strip the second factor off an account — that is the whole point of having one. A wrong password answers 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.

What this does not cover

Two-factor is for staff accounts. Contacts — your users — do not have it. Their security surface is hosted authentication or federated identity, where the second factor, if you want one, is yours to run.