Skip to content

Enroll

POST
/api/auth/mfa/enroll

Generate a new TOTP secret + QR code. Re-enrolling overwrites any unconfirmed prior secret (idempotent) — mfa_enabled stays False until /enroll/confirm verifies a real code from it.

If the user already has mfa_enabled=True, this is a RE-enrollment (e.g. lost device) and requires the same re-auth bar as /disable (current password + a valid current-factor OTP code) — otherwise a bare session could silently swap the TOTP factor to one only an attacker knows, with no password/OTP ever demanded (the forced mid-login path never hits this branch: login() only mints an enroll-purpose pending token when mfa_enabled is already False).

Authorizations

Request Body required

Enroll request. Either an authenticated session (voluntary, MEMBER/ VIEWER) or an mfa_pending/purpose=enroll token (forced mid-login, OWNER/ADMIN) authorizes this — see app/api/mfa.py for the dual-context dependency.

current_password/otp_code are required ONLY when re-enrolling a user who already has mfa_enabled=True (re-auth bar, same as /disable) — a bare session must not be able to silently swap an already-active TOTP factor to one only the caller knows, which would otherwise let a hijacked session (or an XSS the CSRF header exists to resist) lock the real user out and take over their 2FA with no password/OTP ever demanded.

object
current_password
Any of:
string
mfa_pending_token
Any of:
string
otp_code
Any of:
string
>= 6 characters <= 6 characters /^\d{6}$/

Responses

200

Successful Response

QR code (base64 PNG) + manual-entry secret. Deliberately omits the otpauth:// URI as a separate field — it just re-encodes the same secret a second time in the clear for no benefit.

object
qr_code_png_base64
required
string
secret_base32
required
string

422

Validation Error

object
detail
Array<object>
object
loc
required
Array
msg
required
string
type
required
string