Auth API

building

TOTP 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.

POST/v1/auth/two-factor/enable
Request
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"}'
Response
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.

POST/v1/auth/two-factor/verify-totp

Finishes enabling it, and finishes every sign-in afterwards.

Request
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"}'
Response
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:

json
{ "twoFactorRedirect": true, "twoFactorMethods": ["totp"] }

Send them to your second-factor screen and call verify-totp with the six digits.

POST/v1/auth/two-factor/disable

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

StatusCodeMeans
401INVALID_PASSWORDWrong current password on enable or disable
401INVALID_TWO_FACTOR_CODEWrong or expired six digits
400TWO_FACTOR_NOT_ENABLEDVerifying against an account that never enabled it
429—Rate limited; see limits