TrueSpec

Add 2FA/TOTP to Any App

TOTP with otplib, QR code generation, and backup codes

What You’ll Build

After following this guide, you will have a working implementation of add 2fa/totp to any app in your project. Add time-based one-time password (TOTP) two-factor authentication to your app. Compatible with Google Authenticator, Authy, and any TOTP app. Includes QR code setup, token verification, and backup code generation for account recovery.

Use Cases & Problems Solved

  • Protect routes so only authenticated users can access sensitive pages
  • Allow users to sign up, log in, and manage their accounts securely
  • Avoid storing raw passwords or building session management from scratch

Prerequisites

  • Node.js 18+
  • Existing auth system with user database
  • npm packages: otplib, qrcode

Step-by-Step Implementation

Install dependencies

The following snippet shows how to install dependencies. Copy this into your project and adjust the values for your environment.

npm install otplib qrcode crypto

Enable 2FA — generate secret and QR code

The following snippet shows how to enable 2fa — generate secret and qr code. Copy this into your project and adjust the values for your environment.

const { authenticator } = require('otplib');
const QRCode = require('qrcode');

app.post('/auth/2fa/setup', authenticate, async (req, res) => {
  const secret = authenticator.generateSecret();
  await User.update(req.user.id, { twoFactorSecret: secret, twoFactorEnabled: false });

  const otpauthUrl = authenticator.keyuri(req.user.email, 'YourApp', secret);
  const qrCodeDataUrl = await QRCode.toDataURL(otpauthUrl);

  // Generate 10 backup codes
  const backupCodes = Array.from({ length: 10 }, () =>
    require('crypto').randomBytes(4).toString('hex')
  );
  await User.update(req.user.id, { backupCodes: backupCodes.map(c => hashCode(c)) });

  res.json({ qrCode: qrCodeDataUrl, backupCodes });
});

Verify TOTP token on login

The following snippet shows how to verify totp token on login. Copy this into your project and adjust the values for your environment.

app.post('/auth/2fa/verify', async (req, res) => {
  const user = await User.findById(req.session.pendingUserId);
  const { token } = req.body;

  const isValid = authenticator.check(token, user.twoFactorSecret);
  if (!isValid) return res.status(401).json({ error: 'Invalid 2FA code' });

  // Mark 2FA as verified for this session
  req.session.twoFactorVerified = true;
  res.json({ success: true });
});

⚠️ Don’t Do This

❌ Storing the TOTP secret in plaintext without encryption

// Secret stored in plaintext — if DB is breached, all 2FA is compromised
await db.query('UPDATE users SET totp_secret = $1', [secret]);

✅ Encrypt the secret at rest using AES-256

const crypto = require('crypto');
const cipher = crypto.createCipheriv('aes-256-gcm', encryptionKey, iv);
const encrypted = cipher.update(secret, 'utf8', 'hex') + cipher.final('hex');
await db.query('UPDATE users SET totp_secret_enc = $1', [encrypted]);

Testing

Add these tests to verify your add 2fa/totp to any app implementation works correctly:

// __tests__/2fa.test.ts
import { describe, it, expect } from 'vitest';

describe('Add 2FA/TOTP to Any App', () => {
  it('should handle successful authentication', async () => {
    // Test the happy path
    const result = await authenticate({ email: 'test@example.com', password: 'valid' });
    expect(result.user).toBeDefined();
    expect(result.error).toBeNull();
  });

  it('should reject invalid credentials', async () => {
    const result = await authenticate({ email: 'test@example.com', password: 'wrong' });
    expect(result.user).toBeNull();
    expect(result.error).toBeDefined();
  });

  it('should handle missing fields', async () => {
    const result = await authenticate({ email: '', password: '' });
    expect(result.error).toBeDefined();
  });
});

Verification

# Enable 2FA for your test user
# Scan the QR code with Google Authenticator
# Enter the 6-digit code from the app
# Verify login requires the code going forward
# Test a backup code works as fallback

Related Specs

Intermediate

JWT Authentication in Express.js

Access/refresh token pattern with middleware guards and secure cookie storage

Auth & Identity
Intermediate

Secure Session Management with Redis

Express sessions with Redis store, expiration, and fingerprinting

Auth & Identity
Advanced

Role-Based Access Control (RBAC)

Role/permission system with middleware, database schema, and route guards

Auth & Identity