Why TOTP
Time-based One-Time Passwords (TOTP) are the most widely supported second factor — compatible with Google Authenticator, Authy, and most password managers — and don’t require SMS delivery, which has known interception risks.
Generating a Secret and QR Code
const speakeasy = require('speakeasy');
const qrcode = require('qrcode');
async function setupTwoFactor(user) {
const secret = speakeasy.generateSecret({
name: `MyApp (${user.email})`,
});
await db.updateUser(user.id, { twoFactorSecret: secret.base32, twoFactorEnabled: false });
const qrCodeDataUrl = await qrcode.toDataURL(secret.otpauth_url);
return qrCodeDataUrl;
}
Verifying a Code During Setup
function verifyToken(secret, token) {
return speakeasy.totp.verify({
secret,
encoding: 'base32',
token,
window: 1,
});
}
app.post('/2fa/verify-setup', authenticate, async (req, res) => {
const user = await db.getUser(req.user.id);
const isValid = verifyToken(user.twoFactorSecret, req.body.token);
if (!isValid) return res.status(400).json({ error: 'Invalid code' });
await db.updateUser(user.id, { twoFactorEnabled: true });
res.json({ success: true });
});
Requiring 2FA at Login
app.post('/login', async (req, res) => {
const user = await verifyPassword(req.body.email, req.body.password);
if (!user) return res.status(401).json({ error: 'Invalid credentials' });
if (user.twoFactorEnabled) {
const tempToken = generateShortLivedToken(user.id);
return res.json({ requiresTwoFactor: true, tempToken });
}
res.json({ token: generateSessionToken(user.id) });
});
Backup Codes
Always generate a set of one-time backup codes during 2FA setup, hashed and stored the same way passwords are, so users aren’t permanently locked out if they lose their authenticator device.
Common Mistakes
- Not rate-limiting the verification endpoint, allowing brute-force attempts against the 6-digit code
- Storing the TOTP secret in plaintext instead of encrypting it at rest
- No account recovery path other than support tickets, which doesn’t scale and is a social engineering target
Conclusion
TOTP-based 2FA meaningfully reduces account takeover risk for a relatively small implementation cost. Pair it with backup codes and rate limiting, and treat the setup flow itself as security-sensitive code deserving of careful review.