Skip to main content

Two-factor authentication (2FA)

LeonAI supports TOTP (RFC 6238) two-factor authentication for account logins. The TOTP secret is stored AES-256-GCM encrypted server-side, and enrollment yields ten single-use recovery codes. All 2FA management endpoints require a JWT; the final verify step during login is public (it is the second half of the login handshake).

Server requirement

2FA enrollment needs a TOTP_ENCRYPTION_KEY configured on the server. Without it, POST /auth/2fa/setup returns 503.

Enroll​

1. Setup — get a secret​

curl -X POST https://api.short.leonai.io/api/v1/auth/2fa/setup \
-H "Authorization: Bearer <jwt>"

REST: POST /api/v1/auth/2fa/setup → { success, data: { secret, otpauthUrl } }. The plaintext secret (and the otpauthUrl for a QR code) is revealed only here. Load it into an authenticator app (Google Authenticator, 1Password, Authy, …).

2. Enable — confirm a code​

curl -X POST https://api.short.leonai.io/api/v1/auth/2fa/enable \
-H "Authorization: Bearer <jwt>" \
-H "Content-Type: application/json" \
-d '{ "token": "123456" }'

REST: POST /api/v1/auth/2fa/enable. token is the current 6-digit code (^\d{6}$). On success:

{ "success": true, "data": { "enabled": true, "recoveryCodes": ["…", "…"] } }

The ten recovery codes are shown only once — store them somewhere safe. Each is single-use.

Login with 2FA​

When 2FA is enabled, POST /api/v1/auth/login no longer returns tokens. Instead it returns a short-lived (5-minute) pending token:

{ "success": true, "data": { "requiresTwoFactor": true, "pendingToken": "…" } }

Complete the login by verifying a factor:

curl -X POST https://api.short.leonai.io/api/v1/auth/2fa/verify \
-H "Content-Type: application/json" \
-d '{ "pendingToken": "…", "token": "123456" }'

REST: POST /api/v1/auth/2fa/verify (no auth header). Send either token (6-digit TOTP) or recoveryCode (a stored recovery code, single-use). On success it returns the normal login payload — { user, tokens: { accessToken, refreshToken, expiresIn } }. The pending token is single-use. This endpoint is rate limited (20/5 min per IP, plus a per-user failure counter).

Status & disable​

# Is 2FA on?
curl https://api.short.leonai.io/api/v1/auth/2fa/status -H "Authorization: Bearer <jwt>"

# Turn it off (provide a current TOTP code OR a recovery code)
curl -X POST https://api.short.leonai.io/api/v1/auth/2fa/disable \
-H "Authorization: Bearer <jwt>" \
-H "Content-Type: application/json" \
-d '{ "token": "123456" }'
  • GET /api/v1/auth/2fa/status → { success, data: { enabled: boolean } }.
  • POST /api/v1/auth/2fa/disable requires at least one of token or recoveryCode.