# TOTP Implementation in TREK: Complete Two-Factor Authentication Guide

> Learn how TREK implements TOTP 2FA using otplib, AES-256-GCM encryption, and bcrypt hashing. Explore the three-stage REST API for secure authentication.

- Repository: [Maurice/TREK](https://github.com/mauriceboe/TREK)
- Tags: how-to-guide
- Published: 2026-07-09

---

**TREK implements RFC 6238 TOTP-based 2FA using the otplib library with AES-256-GCM encrypted secrets, bcrypt-hashed backup codes, and a three-stage REST API workflow that separates password authentication from token verification.**

The TREK repository provides a production-ready two-factor authentication system built on the Time-based One-Time Password (TOTP) standard. This Node.js/TypeScript implementation leverages the **otplib** library for cryptographic operations while enforcing strict security controls through encrypted storage and constant-time comparison operations.

## TOTP Setup and Enrollment Flow

The enrollment process consists of two distinct phases: temporary secret generation and permanent activation.

### Generating the Initial Secret

When a user initiates 2FA setup via `POST /api/auth/mfa/setup`, the `setupMfa(userId, userEmail)` function in [`server/src/services/authService.ts`](https://github.com/mauriceboe/TREK/blob/main/server/src/services/authService.ts) (lines 1030–1047) executes the following:

1. Calls `authenticator.generateSecret()` to create a random base32 secret
2. Constructs an `otpauth://` URL via `authenticator.keyuri` for QR code generation
3. Stores the pending secret in an in-memory `Map` (`mfaSetupPending`) with a **15-minute TTL** (`MFA_SETUP_TTL_MS`)
4. Generates an SVG QR code asynchronously using the `qrcode` library (`QRCode.toString`)

The endpoint returns the raw secret, the OTPAuth URL, and the QR code SVG to the client.

### Enabling MFA with Verification

The `enableMfa(userId, code)` function (lines 1050–1072) finalizes enrollment:

- Retrieves the pending secret using `getPendingMfaSecret`
- Validates the user-supplied TOTP code with `authenticator.verify({ token, secret })`
- Generates **10 backup codes** (8-character hex strings formatted as `XXXX-XXXX`) via `generateBackupCodes`
- Hashes each backup code with bcrypt (`hashBackupCodeBcrypt`) for secure storage
- Encrypts the TOTP secret using `encryptMfaSecret` before persisting to SQLite
- Sets `mfa_enabled = 1` and stores the JSON-encoded backup code hashes

Upon success, the endpoint returns the plaintext backup codes to the user for safekeeping.

## Login Verification and Token Exchange

TREK implements a stepped authentication flow that prevents session issuance until MFA verification completes.

### Password-Only Authentication Stage

During `loginUser` (MFA branch at lines 1058–1065), after successful password validation:

- If `user.mfa_enabled` is true, TREK issues a **short-lived JWT** (5-minute expiration, `purpose: 'mfa_login'`) instead of a session token
- Returns `{ mfa_required: true, mfa_token }` to the client
- No session cookie or persistent token is issued at this stage

This MFA token serves as a temporary credential that grants access only to the verification endpoint.

### TOTP Code Verification

The `verifyMfaLogin({ mfa_token, code, remember_me })` function (lines 1111–1162) handles the final step:

1. Validates the MFA token JWT and loads the user record
2. Decrypts the stored TOTP secret using `decryptMfaSecret` from [`mfaCrypto.ts`](https://github.com/mauriceboe/TREK/blob/main/mfaCrypto.ts)
3. Attempts verification via `authenticator.verify` against the decrypted secret
4. **Fallback mechanism**: If TOTP verification fails, checks against bcrypt-hashed backup codes using `matchBackupCode` with `crypto.timingSafeEqual`
5. Removes used backup codes from the stored array (single-use enforcement)
6. Updates `last_login` timestamp and issues the final session JWT via `generateToken`

The endpoint returns the standard session token only after successful TOTP or backup code validation.

## Cryptographic Security Layer

### Secret Encryption at Rest

The [`server/src/services/mfaCrypto.ts`](https://github.com/mauriceboe/TREK/blob/main/server/src/services/mfaCrypto.ts) module implements AES-256-GCM encryption:

- `encryptMfaSecret(plain)` generates a random 12-byte IV, encrypts the plaintext, concatenates IV + auth tag + ciphertext, and base64-encodes the result
- `decryptMfaSecret(blob)` reverses this process to recover the original secret for verification

The raw TOTP secret never touches the SQLite database in plaintext form.

### Backup Code Management

Backup codes provide account recovery when authenticator devices are unavailable:

- **Format**: 8-character hexadecimal strings displayed as `XXXX-XXXX`
- **Storage**: Each code is hashed with bcrypt using `hashBackupCodeBcrypt` before storage
- **Verification**: `matchBackupCode` performs constant-time comparison using `crypto.timingSafeEqual` to prevent timing attacks
- **Rotation**: Used codes are immediately removed from the JSON array stored in the database

## REST API Endpoints

The MFA workflow exposes the following endpoints:

- `POST /api/auth/mfa/setup` – Initiates enrollment, returns secret and QR code
- `POST /api/auth/mfa/enable` – Verifies initial code and activates MFA
- `POST /api/auth/login` – Standard login; returns `mfa_token` instead of session when MFA is enabled
- `POST /api/auth/mfa/verify-login` – Validates TOTP or backup code, issues session token
- `POST /api/auth/mfa/disable` – Deactivates MFA and clears stored secrets/backup codes

All endpoints pass through the `RateLimitService` (5 attempts per 15 minutes) and emit audit events via `writeAudit`.

## Code Examples

```bash

# 1️⃣ Start TOTP enrollment

curl -X POST https://trek.example.com/api/auth/mfa/setup \
  -H "Authorization: Bearer $SESSION_JWT"

# Response:

# {

#   "secret":"JBSWY3DPEHPK3PXP",

#   "otpauth_url":"otpauth://totp/TREK:alice%40example.com?secret=JBSWY3DPEHPK3PXP&issuer=TREK",

#   "qr_svg":"<svg ...>"

# }

# 2️⃣ Enable MFA with verification code

curl -X POST https://trek.example.com/api/auth/mfa/enable \
  -H "Authorization: Bearer $SESSION_JWT" \
  -d '{"code":"123456"}' -H "Content-Type: application/json"

# Response:

# {

#   "success":true,

#   "mfa_enabled":true,

#   "backup_codes":["1A2B‑3C4D","5E6F‑7A8B", …]

# }

# 3️⃣ Login (password only)

curl -X POST https://trek.example.com/api/auth/login \
  -d '{"email":"alice@example.com","password":"s3cr3t"}' \
  -H "Content-Type: application/json"

# Response:

# { "mfa_required":true, "mfa_token":"eyJhbGciOi..." }

# 4️⃣ Complete MFA verification

curl -X POST https://trek.example.com/api/auth/mfa/verify-login \
  -d '{"mfa_token":"eyJhbGciOi...","code":"123456"}' \
  -H "Content-Type: application/json"

# Final response:

# {

#   "token":"<session JWT>",

#   "user":{...}

# }

```

## Summary

- **TREK** implements RFC 6238 TOTP using the **otplib** library with `authenticator.generateSecret()` and `authenticator.verify()`
- Secrets are encrypted at rest using **AES-256-GCM** in [`server/src/services/mfaCrypto.ts`](https://github.com/mauriceboe/TREK/blob/main/server/src/services/mfaCrypto.ts) and never stored in plaintext
- The authentication flow uses a **short-lived MFA JWT** (5 minutes) to bridge password verification and TOTP validation
- **10 backup codes** (bcrypt-hashed) provide recovery options, verified using constant-time comparison
- All MFA endpoints enforce rate limiting (5 attempts per 15 minutes) and comprehensive audit logging

## Frequently Asked Questions

### What library does TREK use for TOTP generation?

TREK uses the **otplib** library, specifically the `authenticator` API for generating base32 secrets (`generateSecret`), creating OTPAuth URLs (`keyuri`), and verifying time-based tokens (`verify`).

### How are TOTP secrets stored in the database?

Secrets are encrypted using **AES-256-GCM** before storage. The `encryptMfaSecret` function in [`server/src/services/mfaCrypto.ts`](https://github.com/mauriceboe/TREK/blob/main/server/src/services/mfaCrypto.ts) generates a random 12-byte IV, encrypts the secret, and stores the base64-encoded concatenation of IV, authentication tag, and ciphertext. Decryption occurs only during the verification phase.

### What happens if a user loses their authenticator device?

Users can authenticate using **backup codes** generated during MFA enrollment. These are 8-character hex strings displayed as `XXXX-XXXX`. Each backup code is single-use; TREK removes verified codes from the stored hash array immediately after use. Users should store these codes securely during initial setup.

### How long is the MFA token valid during login?

The MFA token issued during `POST /api/auth/login` is a JWT with a **5-minute expiration** (`purpose: 'mfa_login'`). This limits the window for replay attacks and ensures that abandoned authentication attempts cannot be completed later.