Auth API
buildingTOTP and single-use recovery codes. Enable, verify, and what sign-in returns once it is on.
Two-factor
TOTP and recovery codes. No SMS — it costs money per message, is the weakest common second factor, and its main appeal is familiarity rather than security.
All three calls take a publishable key and a session cookie.
1curl -X POST "$BASE/v1/auth/two-factor/enable" \2 -H "authorization: Bearer $PK" \3 -H "content-type: application/json" \4 -b "session cookie" \5 -d '{"password":"their-current-password"}'1{2 "totpURI": "otpauth://totp/Acme:ada@example.com?secret=…&issuer=Acme",3 "backupCodes": ["3f2a-9c11", "…"]4}Render totpURI as a QR code. The issuer in the authenticator app is your
project's name, so somebody adding a code for their Acme account sees Acme.
The ten backup codes are single-use and shown once. They are the answer to a lost phone, so tell the person to keep them somewhere that is not the phone.
The current password is required: enabling a second factor on a session someone walked away from is how you lock a person out of their own account.
Finishes enabling it, and finishes every sign-in afterwards.
1curl -X POST "$BASE/v1/auth/two-factor/verify-totp" \2 -H "authorization: Bearer $PK" \3 -H "content-type: application/json" \4 -b "session cookie" \5 -d '{"code":"492013"}'1{ "token": "…", "user": { "id": "Nrzmwy…", "email": "ada@example.com" } }This call is what returns the session on a two-factor sign-in.
What sign-in returns once it is on
Sign in stops returning a session and returns this:
{ "twoFactorRedirect": true, "twoFactorMethods": ["totp"] }Send them to your second-factor screen and call verify-totp with the six
digits.
Takes the password again, for the same reason enabling does.
Where the secrets live
The TOTP secret and the backup codes are encrypted at rest, under your project's own signing secret — a key no other project holds. A dump of one project's database is useless against another's.
Errors
| Status | Code | Means |
|---|---|---|
401 | INVALID_PASSWORD | Wrong current password on enable or disable |
401 | INVALID_TWO_FACTOR_CODE | Wrong or expired six digits |
400 | TWO_FACTOR_NOT_ENABLED | Verifying against an account that never enabled it |
429 | — | Rate limited; see limits |