JavaScript / Node.js

Add 2FA to Your Node.js App

A complete guide to integrating Google Authenticator using AuthenticatorAPI.com

Also available in: PHP Python C# Java

This guide covers adding Google Authenticator TOTP two-factor authentication to Node.js applications — from a minimal Express server to a full Next.js or NestJS project. No npm packages required beyond Node's built-in crypto module and the standard fetch API (Node 18+).

👉 Prerequisites: Node.js 18+ (for native fetch). For older Node, use the node-fetch package. A database (PostgreSQL, MongoDB, etc.) to store per-user secrets.
Step-by-Step Integration
1

Create a TOTP Utility Module

// lib/totp.js
'use strict';
const { randomBytes } = require('crypto');

const BASE32_ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';
const API_BASE = 'https://www.authenticatorApi.com';

/**
 * Generates a cryptographically random 32-character Base32 secret.
 */
function generateSecret(length = 32) {
  const bytes = randomBytes(length);
  return Array.from(bytes, b => BASE32_ALPHABET[b % 32]).join('');
}

/**
 * Returns the HTML img tag for the QR code setup page.
 */
async function getQrCodeHtml(appName, userInfo, secret) {
  const params = new URLSearchParams({ AppName: appName, AppInfo: userInfo, SecretCode: secret });
  const res = await fetch(`${API_BASE}/pair.aspx?${params}`);
  if (!res.ok) throw new Error(`Pair API error: ${res.status}`);
  return res.text();
}

/**
 * Returns true if the 6-digit pin is valid for the given secret.
 */
async function validatePin(pin, secret) {
  if (!/^\d{6}$/.test(pin)) return false;
  const params = new URLSearchParams({ Pin: pin, SecretCode: secret });
  const res = await fetch(`${API_BASE}/Validate.aspx?${params}`);
  if (!res.ok) throw new Error(`Validate API error: ${res.status}`);
  const text = await res.text();
  return text.trim().toLowerCase() === 'true';
}

module.exports = { generateSecret, getQrCodeHtml, validatePin };
2

Enrolment Route

// routes/2fa.js
const express = require('express');
const router  = express.Router();
const totp    = require('../lib/totp');
const db      = require('../lib/db');

// GET /2fa/setup — show the QR code
router.get('/setup', async (req, res) => {
  const secret = totp.generateSecret();
  req.session.pending2FASecret = secret;  // store until confirmed

  const qrHtml = await totp.getQrCodeHtml('MyApp', req.user.email, secret);
  res.render('2fa-setup', { qrHtml, secret });
});

// POST /2fa/setup — confirm the user has scanned correctly
router.post('/setup', async (req, res) => {
  const { pin } = req.body;
  const secret  = req.session.pending2FASecret;

  if (await totp.validatePin(pin, secret)) {
    await db.saveEncryptedSecret(req.user.id, secret);
    delete req.session.pending2FASecret;
    res.redirect('/dashboard');
  } else {
    res.render('2fa-setup', { error: 'Invalid code, please try again.', qrHtml: await totp.getQrCodeHtml('MyApp', req.user.email, secret), secret });
  }
});

// POST /2fa/verify — called at login time
router.post('/verify', async (req, res) => {
  const { pin } = req.body;
  const secret  = await db.getDecryptedSecret(req.user.id);

  if (await totp.validatePin(pin, secret)) {
    req.session.twoFactorVerified = true;
    res.redirect('/dashboard');
  } else {
    res.render('2fa-verify', { error: 'Invalid code, please try again.' });
  }
});

module.exports = router;
3

Middleware to Enforce 2FA

// middleware/require2fa.js
module.exports = async function require2fa(req, res, next) {
  if (!req.user) return res.redirect('/login');

  const has2fa = await db.userHas2FA(req.user.id);
  if (has2fa && !req.session.twoFactorVerified) {
    return res.redirect('/2fa/verify');
  }
  next();
};

// In your router:
const require2fa = require('./middleware/require2fa');
app.use('/dashboard', require2fa, dashboardRouter);
app.use('/settings',  require2fa, settingsRouter);
1

Server Action: Generate & Fetch QR Code

// app/actions/totp.ts
'use server';
import { randomBytes } from 'crypto';
import { cookies } from 'next/headers';

const BASE32 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';

export function generateSecret(length = 32): string {
  const bytes = randomBytes(length);
  return Array.from(bytes, b => BASE32[b % 32]).join('');
}

export async function fetchQrHtml(appName: string, email: string, secret: string) {
  const params = new URLSearchParams({ AppName: appName, AppInfo: email, SecretCode: secret });
  const res = await fetch(`https://www.authenticatorApi.com/pair.aspx?${params}`, { cache: 'no-store' });
  return res.text();
}

export async function verifyPin(pin: string, secret: string): Promise<boolean> {
  if (!/^\d{6}$/.test(pin)) return false;
  const params = new URLSearchParams({ Pin: pin, SecretCode: secret });
  const res = await fetch(`https://www.authenticatorApi.com/Validate.aspx?${params}`, { cache: 'no-store' });
  return (await res.text()).trim().toLowerCase() === 'true';
}
2

Setup Page Route Handler

// app/settings/2fa/page.tsx
import { generateSecret, fetchQrHtml } from '@/app/actions/totp';
import TwoFaSetupForm from '@/components/TwoFaSetupForm';

export default async function TwoFaSetupPage() {
  const secret = generateSecret();
  const qrHtml = await fetchQrHtml('MyApp', 'user@example.com', secret);

  return (
    <div>
      <h1>Set Up Two-Factor Authentication</h1>
      <div dangerouslySetInnerHTML={{ __html: qrHtml }} />
      <p>Manual key: <code>{secret}</code></p>
      <TwoFaSetupForm secret={secret} />
    </div>
  );
}
Security Checklist for Node.js