Skip to main content

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:

  1. Step 1POST /api/auth/login with username and password. Instead of returning tokens, the server responds with:

    {
    "status": "mfa_required",
    "mfa_token": "<MFA_TOKEN>"
    }
  2. Step 2POST /api/auth/totp/mfa-login with the mfa_token and 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

EndpointDescription
POST /api/auth/totp/enrollStart TOTP enrollment — returns QR SVG and secret
POST /api/auth/totp/enroll/confirmConfirm enrollment with first TOTP code — returns 10 recovery codes
POST /api/auth/totp/mfa-loginComplete login with TOTP code or recovery code
POST /api/auth/totp/disableDisable 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_KEY is required from v1.8.0 onwards — the stack will not start without it. See Environment Variables.
Security fix in v1.8.0

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.