MFA — Time-Based One-Time Passwords (TOTP)
Starting from v1.8.0, any RAG-DocBot account (admin or standard user) can be protected with a TOTP second factor. TOTP is compatible with standard authenticator apps such as Google Authenticator, Microsoft Authenticator, Authy, and 1Password.
How It Works
When TOTP is enrolled on an account the login flow becomes two steps:
-
Step 1 —
POST /api/auth/loginwith username and password. Instead of returning tokens, the server responds with:{
"status": "mfa_required",
"mfa_token": "<MFA_TOKEN>"
} -
Step 2 —
POST /api/auth/totp/mfa-loginwith themfa_tokenand the current 6-digit TOTP code from the authenticator app. On success, the server returns the access and refresh tokens.
MFA tokens expire after 5 minutes and allow a maximum of 5 attempts before the token is invalidated. A new login (step 1) is required after a lockout.
Enrolling TOTP
Step 1 — Initiate enrollment
curl -s -X POST http://localhost:8000/api/auth/totp/enroll \
-H "Authorization: Bearer <ACCESS_TOKEN>"
The response contains:
qr_code— an SVG image of the QR code to scan with your authenticator app.secret— the raw TOTP secret (for manual entry if the QR code cannot be scanned).
Step 2 — Confirm enrollment
Scan the QR code with your authenticator app, then confirm enrollment with the first generated code:
curl -s -X POST http://localhost:8000/api/auth/totp/enroll/confirm \
-H "Authorization: Bearer <ACCESS_TOKEN>" \
-H "Content-Type: application/json" \
-d '{"totp_code": "123456"}'
The response includes 10 single-use recovery codes. Store these somewhere safe — they are the only way to regain access if you lose your authenticator device. Each code can only be used once.
Completing a TOTP Login
# Step 1: get mfa_token
curl -s -X POST http://localhost:8000/api/auth/login \
-H "Content-Type: application/json" \
-d '{"username": "alice", "password": "s3cure!"}'
# → {"status": "mfa_required", "mfa_token": "<MFA_TOKEN>"}
# Step 2: submit TOTP code
curl -s -X POST http://localhost:8000/api/auth/totp/mfa-login \
-H "Content-Type: application/json" \
-d '{"mfa_token": "<MFA_TOKEN>", "totp_code": "123456"}'
# → {"access_token": "...", "refresh_token": "...", "token_type": "bearer"}
To use a recovery code instead of a TOTP code, pass the recovery code in the totp_code field. Recovery codes are single-use only.
Disabling TOTP
An authenticated user can disable TOTP on their own account:
curl -s -X POST http://localhost:8000/api/auth/totp/disable \
-H "Authorization: Bearer <ACCESS_TOKEN>" \
-H "Content-Type: application/json" \
-d '{"totp_code": "123456"}'
Admins can disable TOTP for any user via the Users section in the admin UI or the user management API.
TOTP Endpoints
| Endpoint | Description |
|---|---|
POST /api/auth/totp/enroll | Start TOTP enrollment — returns QR SVG and secret |
POST /api/auth/totp/enroll/confirm | Confirm enrollment with first TOTP code — returns 10 recovery codes |
POST /api/auth/totp/mfa-login | Complete login with TOTP code or recovery code |
POST /api/auth/totp/disable | Disable TOTP for the authenticated user |
Security Details
- TOTP secrets are encrypted at rest with AES-256-GCM using the
MFA_ENCRYPTION_KEY. - Recovery codes are bcrypt-hashed and each code is invalidated immediately after use.
- MFA tokens (
mfa_token) expire after 5 minutes and lock after 5 failed attempts. - The
MFA_ENCRYPTION_KEYis required from v1.8.0 onwards — the stack will not start without it. See Environment Variables.
In v1.7.x, POST /api/auth/login returned a full session token even when TOTP was enrolled, making MFA bypassable. This is fixed in v1.8.0 — the login endpoint now always requires the second-factor step to be completed before tokens are issued.