TOTP Implementation in TREK: Complete Two-Factor Authentication Guide
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 (lines 1030–1047) executes the following:
- Calls
authenticator.generateSecret()to create a random base32 secret - Constructs an
otpauth://URL viaauthenticator.keyurifor QR code generation - Stores the pending secret in an in-memory
Map(mfaSetupPending) with a 15-minute TTL (MFA_SETUP_TTL_MS) - Generates an SVG QR code asynchronously using the
qrcodelibrary (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) viagenerateBackupCodes - Hashes each backup code with bcrypt (
hashBackupCodeBcrypt) for secure storage - Encrypts the TOTP secret using
encryptMfaSecretbefore persisting to SQLite - Sets
mfa_enabled = 1and 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_enabledis 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:
- Validates the MFA token JWT and loads the user record
- Decrypts the stored TOTP secret using
decryptMfaSecretfrommfaCrypto.ts - Attempts verification via
authenticator.verifyagainst the decrypted secret - Fallback mechanism: If TOTP verification fails, checks against bcrypt-hashed backup codes using
matchBackupCodewithcrypto.timingSafeEqual - Removes used backup codes from the stored array (single-use enforcement)
- Updates
last_logintimestamp and issues the final session JWT viagenerateToken
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 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 resultdecryptMfaSecret(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
hashBackupCodeBcryptbefore storage - Verification:
matchBackupCodeperforms constant-time comparison usingcrypto.timingSafeEqualto 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 codePOST /api/auth/mfa/enable– Verifies initial code and activates MFAPOST /api/auth/login– Standard login; returnsmfa_tokeninstead of session when MFA is enabledPOST /api/auth/mfa/verify-login– Validates TOTP or backup code, issues session tokenPOST /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
# 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()andauthenticator.verify() - Secrets are encrypted at rest using AES-256-GCM in
server/src/services/mfaCrypto.tsand 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 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.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →