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
JWT Authentication in Express.js
Access/refresh token pattern with middleware guards and secure cookie storage
Secure Session Management with Redis
Express sessions with Redis store, expiration, and fingerprinting
Role-Based Access Control (RBAC)
Role/permission system with middleware, database schema, and route guards