Security-hardened TOTP/2FA library for JavaScript and TypeScript
RFC 6238 (TOTP) and RFC 4226 (HOTP) compliant. Zero runtime dependencies. Built-in replay protection. Constant-time verification. Works with Node.js 18+.
Most TOTP libraries give you the basics — generate a code, verify it. But production 2FA needs more:
- Replay protection — prevent the same code from being used twice
- Constant-time verification — prevent timing attacks that leak information
- Secure defaults — preset configurations so you don't need to be a cryptography expert
- Zero dependencies — no supply chain risk for your authentication layer
totp-js is the TypeScript counterpart to totp-impl (Java), sharing the same security-first API design.
# npm
npm install @authcraft/totp-js
# yarn
yarn add @authcraft/totp-js
# pnpm
pnpm add @authcraft/totp-jsimport { TOTP, generateSecret } from '@authcraft/totp-js';
// Generate a secret for the user
const secret = generateSecret();
// Create a TOTP instance
const totp = TOTP.defaultInstance();
// Generate a code
const code = totp.generate(secret);
console.log(code); // "482915"
// Verify a code
const isValid = totp.verify(secret, code);
console.log(isValid); // truePrevent the same OTP from being used twice within its validity window:
import { TOTP, generateSecret, InMemoryReplayGuard } from '@authcraft/totp-js';
const guard = InMemoryReplayGuard.withDefaultRetention();
const totp = TOTP.create({ replayGuard: guard });
const secret = generateSecret();
const code = totp.generate(secret);
// First verification — passes
totp.verify(secret, code, 'user-123'); // true
// Same code, same user — blocked!
totp.verify(secret, code, 'user-123'); // false (replay detected)
// Don't forget to clean up when done
guard.destroy();Generate otpauth:// URIs for QR code scanning with Google Authenticator, Authy, etc.:
import { buildOtpauthUri, generateSecret } from '@authcraft/totp-js';
const secret = generateSecret();
const uri = buildOtpauthUri(secret, 'user@example.com', 'MyApp');
// otpauth://totp/MyApp%3Auser%40example.com?secret=...&issuer=MyApp&algorithm=SHA1&digits=6&period=30import { TOTP, defaultConfig, sha256Config, highSecurityConfig } from '@authcraft/totp-js';
// Default: SHA1, 6 digits, 30s period, drift ±1
const standard = TOTP.create({ ...defaultConfig() });
// SHA-256: recommended for new deployments
const sha256 = TOTP.create({ ...sha256Config() });
// High security: SHA-512, 8 digits
const highSec = TOTP.create({ ...highSecurityConfig() });import { TOTP, Algorithm } from '@authcraft/totp-js';
const totp = TOTP.create({
algorithm: Algorithm.SHA256,
digits: 8,
period: 60, // 60-second window
allowedDrift: 2, // accept codes ±2 periods
});| Parameter | Min | Max | Default |
|---|---|---|---|
period |
15s | 120s | 30s |
digits |
6 | 8 | 6 |
allowedDrift |
0 | 5 | 1 |
Get more context about verification results:
const result = totp.verifyWithDetails(secret, code);
console.log(result);
// { valid: true, timeOffset: 0, message: 'Valid' }
// { valid: false, timeOffset: 0, message: 'Invalid code' }
// { valid: false, timeOffset: 1, message: 'Code already used' }import express from 'express';
import { TOTP, generateSecret, InMemoryReplayGuard, buildOtpauthUri } from '@authcraft/totp-js';
const app = express();
app.use(express.json());
const guard = InMemoryReplayGuard.withDefaultRetention();
const totp = TOTP.create({ replayGuard: guard });
// Setup 2FA for a user
app.post('/2fa/setup', (req, res) => {
const secret = generateSecret();
const uri = buildOtpauthUri(secret, req.body.email, 'MyApp');
// Store secret in your database
res.json({ secret, qrUri: uri });
});
// Verify 2FA code
app.post('/2fa/verify', (req, res) => {
const { userId, code, secret } = req.body;
const isValid = totp.verify(secret, code, userId);
res.json({ valid: isValid });
});// app/api/2fa/verify/route.ts
import { NextResponse } from 'next/server';
import { TOTP, InMemoryReplayGuard } from '@authcraft/totp-js';
const guard = InMemoryReplayGuard.withDefaultRetention();
const totp = TOTP.create({ replayGuard: guard });
export async function POST(request: Request) {
const { secret, code, userId } = await request.json();
const isValid = totp.verify(secret, code, userId);
return NextResponse.json({ valid: isValid });
}import { Injectable } from '@nestjs/common';
import { TOTP, generateSecret, InMemoryReplayGuard, buildOtpauthUri } from '@authcraft/totp-js';
@Injectable()
export class TwoFactorService {
private readonly totp: TOTP;
private readonly guard: InMemoryReplayGuard;
constructor() {
this.guard = InMemoryReplayGuard.withDefaultRetention();
this.totp = TOTP.create({ replayGuard: this.guard });
}
setup(email: string) {
const secret = generateSecret();
const uri = buildOtpauthUri(secret, email, 'MyApp');
return { secret, qrUri: uri };
}
verify(secret: string, code: string, userId: string): boolean {
return this.totp.verify(secret, code, userId);
}
}import Fastify from 'fastify';
import { TOTP, generateSecret, InMemoryReplayGuard } from '@authcraft/totp-js';
const app = Fastify();
const guard = InMemoryReplayGuard.withDefaultRetention();
const totp = TOTP.create({ replayGuard: guard });
app.post('/2fa/verify', async (request, reply) => {
const { secret, code, userId } = request.body as any;
const isValid = totp.verify(secret, code, userId);
return { valid: isValid };
});import { generateSecret, generateRawSecret, isValidSecret, Algorithm } from '@authcraft/totp-js';
// Default (SHA1, 20 bytes)
const secret = generateSecret();
// Algorithm-appropriate size
const sha256Secret = generateSecret(Algorithm.SHA256); // 32 bytes
const sha512Secret = generateSecret(Algorithm.SHA512); // 64 bytes
// Raw bytes for custom encoding
const rawBytes = generateRawSecret(32);
// Validate existing secrets
isValidSecret(secret); // true
isValidSecret('abc'); // false (too short)| Method | Description |
|---|---|
TOTP.create(options?) |
Create with custom options |
TOTP.defaultInstance() |
Create with defaults (SHA1, 6 digits, 30s) |
generate(secret) |
Generate code for current time |
generateAt(secret, timestamp) |
Generate code for specific timestamp (ms) |
generateForCounter(secret, counter) |
Generate code for specific counter |
verify(secret, code, userId?) |
Verify code (with optional replay guard) |
verifyWithDetails(secret, code, userId?) |
Verify with detailed result |
getCurrentCounter() |
Get current time counter |
getSecondsRemaining() |
Seconds until next code |
| Preset | Algorithm | Digits | Period | Drift |
|---|---|---|---|---|
defaultConfig() |
SHA-1 | 6 | 30s | ±1 |
sha256Config() |
SHA-256 | 6 | 30s | ±1 |
highSecurityConfig() |
SHA-512 | 8 | 30s | ±1 |
| Algorithm | Key Size | Recommended For |
|---|---|---|
| SHA-1 | 20 bytes | Legacy compatibility (Google Authenticator default) |
| SHA-256 | 32 bytes | New deployments (recommended) |
| SHA-512 | 64 bytes | Maximum security |
- Constant-time comparison using
crypto.timingSafeEqual()— prevents timing attacks - Replay protection via
InMemoryReplayGuard— prevents code reuse - Secret validation — enforces minimum 128-bit entropy (16 bytes)
- No sensitive data in errors — error messages never contain secrets or codes
- Zero runtime dependencies — minimized attack surface
git clone https://github.com/Pratiyush/totp-js.git
cd totp-js
npm install
npm run build
npm test- Documentation — GitHub Pages with interactive demo
- npm —
npm install @authcraft/totp-js - GitHub — Source code, issues, PRs
- totp-impl — Java counterpart (Maven Central)
See CONTRIBUTING.md for guidelines.
MIT — Pratiyush Kumar Singh