Skip to main content

Two-Factor Authentication (2FA)

Time-based One-Time Password (TOTP) implementation using the OTPAuth library.

Architecture

  • Library: otpauth (TOTP, SHA1, 6 digits, 30-second period)
  • Backup codes: 10 codes, 8-char hex via crypto.randomBytes(4)
  • Storage: User model fields — twoFactorSecret, twoFactorEnabled, twoFactorBackupCodes

Authentication Flow

Login Request
├─ Credentials valid + 2FA disabled → Return JWT tokens
└─ Credentials valid + 2FA enabled → Return { requiresTwoFactor: true, tempUserId }
└─ Client submits TOTP code
├─ Valid TOTP → Return JWT tokens
└─ Invalid TOTP → Check backup codes
├─ Valid backup → Consume code, return JWT tokens
└─ Invalid → 401 Unauthorized

API Endpoints

MethodEndpointDescriptionAuth
GET/auth/2fa/setupGenerate QR code URI + secretJWT
POST/auth/2fa/enableEnable 2FA (requires valid token)JWT
DELETE/auth/2fa/disableDisable 2FA (requires valid token)JWT
POST/auth/2fa/verifyVerify TOTP during loginTemp token

Service Methods

TwoFactorService

Source: src/auth/two-factor.service.ts

MethodDescription
generateSecret()Creates TOTP secret, returns otpauth URI for QR
verifyToken(secret, token)Validates TOTP with window=5 (±2.5 minutes)
enableTwoFactor(userId, token)Verifies token then enables, returns backup codes
disableTwoFactor(userId, token)Verifies token then disables 2FA
verifyTwoFactor(userId, token)Checks TOTP first, then backup codes
generateBackupCodes()Generates 10 random 8-char hex codes
regenerateBackupCodes(userId)Replaces all backup codes

Backup Codes

  • Generated with crypto.randomBytes(4).toString('hex') (8 hex characters each)
  • Stored as array on the User document
  • Consumed on use — removed from array after successful verification
  • Can be regenerated via regenerateBackupCodes()

Security Notes

Critical
  • TOTP secrets are stored in the database — encrypt at rest via MongoDB encryption
  • Backup codes are single-use and removed from the array immediately
  • Verification window of 5 allows for minor clock skew but increases brute-force surface
  • The tempUserId returned during 2FA challenge should have short TTL (5 minutes)
  • AuthModule — Login flow integration
  • UsersModule — User model with 2FA fields