HeoLab
ToolsBlogAboutContact
HeoLab

Free developer tools with AI enhancement. Built for developers who ship.

Tools

  • JSON Formatter
  • JWT Decoder
  • Base64 Encoder
  • Timestamp Converter
  • Regex Tester
  • All Tools →

Resources

  • Blog
  • What is JSON?
  • JWT Deep Dive
  • Base64 Explained

Company

  • About
  • Contact
  • Privacy Policy
  • Terms of Service

© 2026 HeoLab. All rights reserved.

Tools work in your browser. Zero data retention.

HomeBlogHow to Implement TOTP Two-Factor Authentication in Your App
Table of Contents▾
  • How TOTP Works (RFC 6238)
  • Implementing TOTP in Node.js
  • Generating a QR Code for Setup
  • Generating a Secure Secret
  • Database Schema
  • Security Considerations
  • Libraries to Use
security#totp#2fa#authentication

How to Implement TOTP Two-Factor Authentication in Your App

A complete technical guide to implementing time-based one-time passwords (TOTP/RFC 6238) — how the algorithm works and how to add 2FA to any app.

Trong Ngo
February 23, 2026
3 min read

Two-factor authentication (2FA) is no longer optional for serious applications. TOTP — Time-based One-Time Passwords — is the standard behind every authenticator app (Google Authenticator, Authy, 1Password). This guide explains how it works and how to implement it.

How TOTP Works (RFC 6238)

TOTP generates a 6-digit code that changes every 30 seconds. The magic: both the server and the authenticator app compute the same code independently using two shared inputs:

  1. The shared secret — a Base32-encoded random key (shown as a QR code during setup)
  2. The current time — rounded down to the nearest 30-second window

The algorithm:

counter = floor(unix_timestamp / 30)
hmac    = HMAC-SHA1(secret, counter_as_8_bytes)
offset  = last nibble of hmac
code    = (hmac[offset..offset+4] & 0x7FFFFFFF) % 1_000_000

That's the entire TOTP algorithm. The HMAC-SHA1 and bit manipulation is why you can't predict future codes without the secret.

Implementing TOTP in Node.js

const crypto = require('crypto');

function generateTOTP(base32Secret, window = 0) {
  // Decode Base32 secret
  const secret = base32Decode(base32Secret);

  // Current 30-second window
  const counter = Math.floor(Date.now() / 1000 / 30) + window;

  // Pack counter as 8-byte big-endian buffer
  const buf = Buffer.alloc(8);
  buf.writeUInt32BE(Math.floor(counter / 2 ** 32), 0);
  buf.writeUInt32BE(counter >>> 0, 4);

  // HMAC-SHA1
  const hmac = crypto.createHmac('sha1', secret).update(buf).digest();

  // Dynamic truncation
  const offset = hmac[hmac.length - 1] & 0x0f;
  const code = ((hmac.readUInt32BE(offset) & 0x7fffffff) % 1000000)
    .toString().padStart(6, '0');

  return code;
}

// Validate with ±1 window tolerance for clock skew
function validateTOTP(secret, userCode) {
  return [-1, 0, 1].some(w => generateTOTP(secret, w) === userCode);
}

Generating a QR Code for Setup

The QR code encodes an otpauth:// URI:

const QRCode = require('qrcode');

function getTOTPQRCodeURL(user, secret, issuer = 'MyApp') {
  const otpauth = `otpauth://totp/${issuer}:${user}?secret=${secret}&issuer=${issuer}&algorithm=SHA1&digits=6&period=30`;
  return QRCode.toDataURL(otpauth);
}

Generating a Secure Secret

const crypto = require('crypto');

function generateSecret() {
  const bytes = crypto.randomBytes(20); // 160 bits
  return base32Encode(bytes); // e.g. 'JBSWY3DPEHPK3PXP'
}

Database Schema

ALTER TABLE users ADD COLUMN totp_secret TEXT;
ALTER TABLE users ADD COLUMN totp_enabled BOOLEAN DEFAULT FALSE;
ALTER TABLE users ADD COLUMN totp_verified_at TIMESTAMPTZ;

-- Prevent TOTP replay attacks
CREATE TABLE totp_used_codes (
  user_id UUID REFERENCES users(id),
  code TEXT NOT NULL,
  used_at TIMESTAMPTZ DEFAULT NOW(),
  PRIMARY KEY (user_id, code)
);

Security Considerations

Replay attacks — once a code is used, mark it in the database for the 90-second window (±1 window) to prevent reuse.

Backup codes — generate 8-10 single-use backup codes when the user enables 2FA. Store them hashed (bcrypt).

Rate limiting — limit TOTP verification attempts to prevent brute-forcing. Only 1,000,000 possible codes exist.

Clock skew — allow ±1 window (90 seconds total) for devices with slightly incorrect clocks.

Secret storage — store the TOTP secret encrypted in your database (not just hashed — you need to retrieve it).

Libraries to Use

Instead of rolling your own, use well-tested libraries:

  • Node.js: otplib, speakeasy, @otplib/preset-default
  • Python: pyotp
  • Go: github.com/pquerna/otp
  • PHP: sonata-project/google-authenticator

Use the TOTP Generator tool to test codes with any Base32 secret directly in your browser — useful for debugging your implementation.

Try These Tools

TOTP Generator

Generate live TOTP codes from any Base32 secret. Compatible with Google Authenticator and Authy.

HMAC Generator

Generate HMAC-SHA256 and HMAC-SHA512 signatures for message authentication and API request signing.

Related Articles

Understanding OAuth 2.0 and OpenID Connect: A Developer's Guide

3 min read

The Developer's Guide to Password Security

4 min read

Content Security Policy (CSP): A Practical Guide for Developers

3 min read

Back to Blog

Table of Contents

  • How TOTP Works (RFC 6238)
  • Implementing TOTP in Node.js
  • Generating a QR Code for Setup
  • Generating a Secure Secret
  • Database Schema
  • Security Considerations
  • Libraries to Use

Related Articles

Understanding OAuth 2.0 and OpenID Connect: A Developer's Guide

3 min read

The Developer's Guide to Password Security

4 min read

Content Security Policy (CSP): A Practical Guide for Developers

3 min read