Two Factor Documentation
Overview
Two-Factor Authentication (2FA) adds an additional security layer to user authentication using Time-based One-Time Password (TOTP) algorithm. This implementation supports both authenticator apps (Google Authenticator, Authy, etc.) and backup codes for account recovery.
Key Features:
- TOTP-based verification (RFC 6238 compliant)
- 8 one-time backup codes for recovery
- Admin-controlled force setup
- AES-256 encryption for secret storage
- Challenge-based verification flow
- Session revocation on security changes
- Account protection with failed attempts tracking
- 2FA protection on sensitive operations (login, password change, password reset, disable 2FA)
Related Documents
- Authentication Documentation - JWT token management and login flows
- Security and Middleware Documentation - Rate limiting and security headers
- Cache Documentation - Redis implementation for challenge tokens
- Activity Log Documentation - Events tracking
Table of Contents
- Overview
- Related Documents
- Configuration
- Security Features
- Where 2FA is Used
- Authentication Flows
- Error Handling
- Contribution
Configuration
Environment Variables
AUTH_TWO_FACTOR_ISSUER=YourAppName
AUTH_TWO_FACTOR_ENCRYPTION_KEY=your-32-character-encryption-key-hereConfiguration Details
Located in src/configs/auth.config.ts:
| Setting | Value | Description |
|---|---|---|
issuer | YourAppName | Displayed in authenticator apps |
digits | 6 | TOTP code length (standard) |
step | 30 | Time window in seconds |
window | 1 | Clock skew tolerance (±30 seconds) |
secretLength | 32 | Base32 secret length |
challengeTtlInMs | 300000 | Challenge token TTL (5 minutes) |
backupCodes.count | 8 | Number of backup codes generated |
backupCodes.length | 10 | Characters per backup code (A-Z, 0-9) |
maxAttempt | 5 | Maximum failed verification attempts before lock |
lockAttemptDuration | 120000 | Base lock duration in milliseconds (2 minutes) |
Security Features
Failed Attempts Protection
The system tracks failed 2FA verification attempts to protect against brute force attacks:
How it works:
- Each failed TOTP code or backup code verification increments the attempt counter
- Counter is stored in the
TwoFactor.attemptfield - Counter resets to 0 when user successfully verifies with valid code or backup code
- Both TOTP codes and backup codes share the same counter (combined limit)
Counter tracking:
Attempt 1 (TOTP failed) → attempt = 1
Attempt 2 (Backup code failed) → attempt = 2
Attempt 3 (TOTP failed) → attempt = 3
Attempt 4 (TOTP success) → attempt = 0 (reset)Temporary Lock Mechanism
When a user reaches the maximum allowed attempts (5 failed verifications), the system temporarily locks 2FA verification with exponential backoff.
How it works:
- Lock check happens before verification attempt
- If locked, return error with
retryAfterSeconds(HTTP 429) - If not locked, proceed with verification
- If verification fails:
- Increment attempt counter first
- After increment, check if counter reached max (5)
- If reached max, set lock in cache with exponential TTL
- Return invalid code error (HTTP 401)
- Lock is stored in Redis cache with automatic expiration
- Lock duration increases exponentially based on attempt count
Lock timing:
- Lock is set after the 5th failed attempt
- Lock prevents next verification attempt
- User receives HTTP 429 on next attempt (not the 5th)
Lock duration calculation:
- Formula:
TTL = 2^(attempt / maxAttempt) × lockAttemptDuration - Base lock duration: 2 minutes (configurable via
lockAttemptDuration)
Lock duration examples:
After 5th failed attempt (attempt=5):
TTL = 2^(5/5) × 2 minutes = 2^1 × 2 = 4 minutes
After 6th failed attempt (attempt=6):
TTL = 2^(6/5) × 2 minutes = 2^1.2 × 2 ≈ 4.6 minutes
After 7th failed attempt (attempt=7):
TTL = 2^(7/5) × 2 minutes = 2^1.4 × 2 ≈ 5.3 minutesUser experience flow:
- User enters wrong code 5 times → gets HTTP 401 (invalid code)
- Lock is set in background
- User tries again (6th attempt) → gets HTTP 429 with
retryAfterSeconds: 240(4 minutes) - User waits 4 minutes
- Lock expires automatically
- User can try again
- If fails again, new lock with longer duration (exponential backoff)
Recovery process:
- Lock automatically expires after TTL duration (no admin intervention needed)
- Attempt counter persists in database until successful verification
- Each subsequent lock (after retry) increases duration exponentially
- Counter resets to 0 only on successful verification
- Admin can force reset 2FA to clear both counter and lock
Where 2FA is Used
Shared Endpoints (User Operations)
2FA Management:
GET /shared/user/2fa/status- Check current 2FA statusPOST /shared/user/2fa/setup- Get TOTP secret and otpauthUrlPOST /shared/user/2fa/enable- Enable 2FA with code verificationDELETE /shared/user/2fa/disable- Disable 2FA (requires 2FA verification)POST /shared/user/2fa/regenerate-backup-codes- Regenerate backup codes
Password Operations (require 2FA if enabled):
PUT /shared/user/password/change- Change password (requires 2FA verification if enabled)
Public Endpoints
Login Flow:
POST /public/user/login/credential- Login with email/passwordPOST /public/user/login/social/google- Login with Google OAuthPOST /public/user/login/social/apple- Login with Apple Sign InPOST /public/user/login/2fa/verify- Verify TOTP code or backup codePOST /public/user/login/2fa/enable- Complete forced 2FA setup during login
Password Recovery (require 2FA if enabled):
PUT /public/user/password/reset- Reset password (requires 2FA verification if enabled)
Admin Endpoints
PATCH /admin/user/update/:userId/2fa/reset- Force reset user’s 2FA (clears lock and resets attempts)
Note: See Swagger documentation for detailed request/response schemas.
Authentication Flows
Setup Flow
User enables 2FA for their account:
Login Flow (2FA Enabled)
User logs in with 2FA enabled:
Admin Force Setup Flow
Admin forces user to setup 2FA on next login:
Backup Code Usage Flow
User uses backup code when authenticator app is unavailable:
Temporary Lock Flow
System behavior when user reaches maximum attempts:
Admin Reset 2FA Flow
Admin can force reset user’s 2FA if needed (optional, user can also wait for lock to expire):
Password Operations with 2FA Flow
When user has 2FA enabled, password operations require additional verification:
Change Password:
Reset Password (Forgot Password):
Disable 2FA:
Error Handling
HTTP Status Codes
| Status | Error Code | Description |
|---|---|---|
| 400 | twoFactorNotEnabled | 2FA not enabled for this user |
| 400 | twoFactorAlreadyEnabled | 2FA already active |
| 400 | twoFactorRequiredSetup | Must complete setup first |
| 400 | twoFactorNotRequiredSetup | Setup already completed |
| 401 | twoFactorInvalid | Invalid TOTP code or backup code |
| 401 | twoFactorChallengeInvalid | Challenge token expired or invalid |
| 429 | twoFactorAttemptTemporaryLock | Too many failed attempts, temporarily locked with retryAfterSeconds |
| 403 | inactiveForbidden | Account inactive |
| 403 | emailNotVerified | Email not verified |
| 404 | notFound | User not found |
Note on twoFactorAttemptTemporaryLock: This error occurs when user tries to verify 2FA while locked. Response includes retryAfterSeconds indicating when user can retry. Lock is set automatically after the 5th failed attempt (when attempt counter reaches 5). Lock duration increases exponentially with each subsequent lockout.
Contribution
Special thanks to [ak2g][ref-contributor-ak2g] for main contributor for this feature.