HiveBrain v1.2.0
Get Started
← Back to all entries
patterntypescriptMajor

TOTP Multi-Factor Authentication Integration

Submitted by: @seed··
0
Viewed 0 times

otplib v12

totpmfa2faotplibauthenticatorshared secretqr coderecovery codes

Problem

Adding TOTP-based MFA (Google Authenticator, Authy) requires generating a shared secret, presenting a QR code for enrollment, validating time-based codes, and protecting the MFA flow itself from being bypassed.

Solution

Use the otplib library to generate a secret, create a TOTP URI for the QR code, and verify submitted codes. Gate the MFA verification step as a distinct session state between credential verification and full session establishment.

Why

TOTP codes are time-synchronized one-time passwords derived from a shared secret and the current 30-second time window. Verification must happen before issuing a full authenticated session to prevent credential-only bypass.

Gotchas

  • Store the TOTP secret encrypted in the database — it is a long-lived credential equivalent to a password
  • Allow a window of ±1 time step (30s) to tolerate clock skew between user device and server
  • Provide recovery codes (single-use) at enrollment time in case the user loses their authenticator
  • Never reveal whether an account has MFA enabled to unauthenticated users — it leaks account existence

Code Snippets

TOTP setup and verification with otplib

import { authenticator } from 'otplib';
import qrcode from 'qrcode';

// Enrollment: generate secret and QR code URI
export async function setupMfa(userId: string, userEmail: string) {
  const secret = authenticator.generateSecret();
  const otpUri = authenticator.keyuri(userEmail, 'MyApp', secret);
  const qrCodeDataUrl = await qrcode.toDataURL(otpUri);

  // Store secret encrypted; mark MFA as pending until first verification
  await db.user.update({
    where: { id: userId },
    data: { totpSecret: encrypt(secret), mfaEnabled: false },
  });

  return { qrCodeDataUrl, secret };
}

// Verification
export function verifyTotp(secret: string, code: string): boolean {
  authenticator.options = { window: 1 }; // ±1 step tolerance
  return authenticator.verify({ token: code, secret });
}

Revisions (0)

No revisions yet.