Skip to Content
Two Factor Documentation

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)

Table of Contents

Configuration

Environment Variables

AUTH_TWO_FACTOR_ISSUER=YourAppName AUTH_TWO_FACTOR_ENCRYPTION_KEY=your-32-character-encryption-key-here

Configuration Details

Located in src/configs/auth.config.ts:

SettingValueDescription
issuerYourAppNameDisplayed in authenticator apps
digits6TOTP code length (standard)
step30Time window in seconds
window1Clock skew tolerance (±30 seconds)
secretLength32Base32 secret length
challengeTtlInMs300000Challenge token TTL (5 minutes)
backupCodes.count8Number of backup codes generated
backupCodes.length10Characters per backup code (A-Z, 0-9)
maxAttempt5Maximum failed verification attempts before lock
lockAttemptDuration120000Base 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.attempt field
  • 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:

  1. Lock check happens before verification attempt
  2. If locked, return error with retryAfterSeconds (HTTP 429)
  3. If not locked, proceed with verification
  4. 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)
  1. Lock is stored in Redis cache with automatic expiration
  2. 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 minutes

User experience flow:

  1. User enters wrong code 5 times → gets HTTP 401 (invalid code)
  2. Lock is set in background
  3. User tries again (6th attempt) → gets HTTP 429 with retryAfterSeconds: 240 (4 minutes)
  4. User waits 4 minutes
  5. Lock expires automatically
  6. User can try again
  7. 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 status
  • POST /shared/user/2fa/setup - Get TOTP secret and otpauthUrl
  • POST /shared/user/2fa/enable - Enable 2FA with code verification
  • DELETE /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/password
  • POST /public/user/login/social/google - Login with Google OAuth
  • POST /public/user/login/social/apple - Login with Apple Sign In
  • POST /public/user/login/2fa/verify - Verify TOTP code or backup code
  • POST /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

StatusError CodeDescription
400twoFactorNotEnabled2FA not enabled for this user
400twoFactorAlreadyEnabled2FA already active
400twoFactorRequiredSetupMust complete setup first
400twoFactorNotRequiredSetupSetup already completed
401twoFactorInvalidInvalid TOTP code or backup code
401twoFactorChallengeInvalidChallenge token expired or invalid
429twoFactorAttemptTemporaryLockToo many failed attempts, temporarily locked with retryAfterSeconds
403inactiveForbiddenAccount inactive
403emailNotVerifiedEmail not verified
404notFoundUser 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.

Last updated on