Observed since 20 Oct, 19:12 UTC
No superseded metadata states collected. Unchanged observations do not create new revisions.
2fa-lib
Tiny zero-dependency TOTP and HOTP library for Node.js, browsers, Deno, Bun, Cloudflare Workers and edge runtimes. RFC 6238 / RFC 4226 compliant. Google Authenticator compatible two-factor authentication (2FA / MFA) for JavaScript and TypeScript.
Two-Factor Authentication (2FA / MFA) made simple. Works with Google Authenticator, Microsoft Authenticator, Authy, 1Password, Bitwarden, Duo, and any RFC 6238 compatible app.
Install • Quick Start • API • Examples • Compatibility • Why 2fa-lib?
@types package neededotpauth:// URIs for any QR libraryotpauth:// URIs from other appsimport and requirenpm install 2fa-lib
yarn add 2fa-lib
pnpm add 2fa-lib
bun add 2fa-lib
import { generateSecret, totp, verify, buildURI } from '2fa-lib';
// 1. Create a secret for the user (store it encrypted in your DB)
const secret = generateSecret();
// 2. Build a provisioning URI → render as QR code in your UI
const uri = buildURI({
secret,
label: 'alice@example.com',
issuer: 'MyApp',
});
// 3. Generate the current 6-digit code
const code = await totp(secret);
console.log(code); // → "123456"
// 4. Verify a user-submitted code (allows ±1 step clock drift)
const result = await verify('123456', secret, { window: 1 });
if (result) {
console.log('✅ Valid! Drift:', result.delta);
} else {
console.log('❌ Invalid code');
}
Full TypeScript types are bundled — no extra @types/2fa-lib package required.
import { totp, verify, type VerifyResult } from '2fa-lib';
const code: string = await totp('JBSWY3DPEHPK3PXP');
const result: VerifyResult | null = await verify(code, 'JBSWY3DPEHPK3PXP');
| App | Status | Notes |
|---|---|---|
| Google Authenticator | ✅ Full | All algorithms supported |
| Microsoft Authenticator | ✅ Full | Use microsoftAuthenticatorURI() (forces SHA1/6/30) |
| Authy | ✅ Full | All algorithms supported |
| 1Password | ✅ Full | All algorithms supported |
| Bitwarden | ✅ Full | All algorithms supported |
| Duo Mobile | ✅ Full | Standard TOTP |
| FreeOTP / FreeOTP+ | ✅ Full | All algorithms supported |
| Steam Guard | ✅ Full | Use steamTOTP() for 5-char codes |
⚠️ Microsoft Authenticator silently ignores
algorithm,digits, andperiodURI parameters — it always uses SHA1, 6 digits, 30 seconds. Use the dedicatedmicrosoftAuthenticatorURI()helper to guarantee compatibility.
totp(secret, options?)Generate a time-based one-time password (TOTP).
await totp('JBSWY3DPEHPK3PXP');
await totp(secret, { digits: 8, period: 60, algorithm: 'SHA256' });
hotp(secret, counter, options?)Generate an HMAC-based one-time password (HOTP).
await hotp('JBSWY3DPEHPK3PXP', 0); // → "755224"
verify(token, secret, options?)Verify a token. Returns { delta } or null. The window option allows ±N steps of clock drift (default 1 = ±30s).
const result = await verify('123456', secret, { window: 1 });
if (result) console.log('Valid, drift:', result.delta);
generateSecret(length?)Generate a cryptographically secure base32-encoded secret. Default length: 20 bytes.
const secret = generateSecret(); // → "JBSWY3DPEHPK3PXP..."
timeRemaining(options?)Seconds remaining in the current TOTP step. Useful for countdown UIs.
console.log(`${timeRemaining()}s until next code`);
totpPair(secret, options?)Returns the current and next TOTP codes — useful for "next code" UI hints.
const { current, next } = await totpPair(secret);
buildURI(options)Build an otpauth:// URI for QR code provisioning.
const uri = buildURI({
secret,
label: 'alice@example.com',
issuer: 'MyApp',
algorithm: 'SHA1', // 'SHA1' | 'SHA256' | 'SHA512'
digits: 6,
period: 30,
});
microsoftAuthenticatorURI({ secret, label, issuer })Build a URI guaranteed to work with Microsoft Authenticator.
const uri = microsoftAuthenticatorURI({
secret,
label: 'alice@example.com',
issuer: 'MyApp',
});
parseURI(uri)Parse an otpauth:// URI into its components — perfect for importing secrets from QR codes.
const parsed = parseURI('otpauth://totp/MyApp:alice?secret=...&issuer=MyApp');
// { type, label, issuer, secret, algorithm, digits, period, counter? }
generateBackupCodes(count?, length?)Generate human-friendly single-use recovery codes (no confusing characters).
const codes = generateBackupCodes(10, 8); // 10 codes, 8 chars each
hashBackupCode(code) & verifyBackupCode(code, hashes)Store backup codes as SHA-256 hashes in your database, never plaintext.
const codes = generateBackupCodes();
const hashes = await Promise.all(codes.map(hashBackupCode));
// store `hashes` in DB
// Later...
const idx = await verifyBackupCode(userInput, hashes);
if (idx !== -1) {
// Valid! Remove hashes[idx] so it can't be reused
}
createRateLimiter({ max, windowMs })Built-in brute-force protection.
const limiter = createRateLimiter({ max: 5, windowMs: 60_000 });
if (!limiter.allow(userId)) {
throw new Error('Too many attempts. Try again later.');
}
createReplayGuard()Prevent the same TOTP code from being used twice within its valid window.
const guard = createReplayGuard();
const result = await verify(token, secret);
if (!result) throw new Error('Invalid');
const step = Math.floor(Date.now() / 1000 / 30) + result.delta;
if (!guard.accept(userId, step)) {
throw new Error('Token already used');
}
isValidTokenFormat(token, options?)Pre-validate token shape before calling verify().
if (!isValidTokenFormat(input)) return res.status(400).send('Bad format');
steamTOTP(secret, options?)Generate a 5-character Steam Guard code.
const code = await steamTOTP(secret); // → "K8R3F"
import express from 'express';
import QRCode from 'qrcode';
import { generateSecret, buildURI, verify } from '2fa-lib';
const app = express();
app.post('/2fa/setup', async (req, res) => {
const secret = generateSecret();
await db.users.update(req.user.id, { totpSecret: secret, totpEnabled: false });
const uri = buildURI({
secret,
label: req.user.email,
issuer: 'MyApp',
});
const qrDataUrl = await QRCode.toDataURL(uri);
res.json({ qrDataUrl, secret });
});
app.post('/2fa/verify', async (req, res) => {
const user = await db.users.findById(req.user.id);
const result = await verify(req.body.code, user.totpSecret, { window: 1 });
if (!result) return res.status(401).json({ error: 'Invalid code' });
await db.users.update(user.id, { totpEnabled: true });
res.json({ success: true });
});
// app/api/login/route.ts
import { verify, createRateLimiter } from '2fa-lib';
const limiter = createRateLimiter({ max: 5, windowMs: 60_000 });
export async function POST(req: Request) {
const { email, password, code } = await req.json();
if (!limiter.allow(email)) {
return Response.json({ error: 'Too many attempts' }, { status: 429 });
}
const user = await db.users.findByEmail(email);
// ... validate password ...
if (user.totpEnabled) {
const result = await verify(code, user.totpSecret);
if (!result) return Response.json({ error: 'Invalid 2FA code' }, { status: 401 });
}
return Response.json({ token: createSessionToken(user) });
}
import { totp, verify } from '2fa-lib';
export default {
async fetch(request, env) {
const code = await totp(env.USER_SECRET);
return new Response(code);
},
};
| Feature | 2fa-lib | otplib | speakeasy | otpauth |
|---|---|---|---|---|
| Zero dependencies | ✅ | ❌ | ❌ | ✅ |
| TypeScript types built-in | ✅ | ✅ | ❌ | ✅ |
| Edge runtime support | ✅ | ⚠️ | ❌ | ✅ |
| Microsoft Authenticator helper | ✅ | ❌ | ❌ | ❌ |
otpauth:// URI parser | ✅ | ❌ | ❌ | ✅ |
| Backup code hashing | ✅ | ❌ | ❌ | ❌ |
| Built-in rate limiter | ✅ | ❌ | ❌ | ❌ |
| Replay protection | ✅ | ❌ | ❌ | ❌ |
| Steam Guard | ✅ | ❌ | ❌ | ❌ |
| Bundle size (min+gzip) | <5 KB | ~15 KB | ~30 KB | ~8 KB |
window: 1 (±30s) to handle clock skew between server and phone.npm test
The library is tested against the official RFC 4226 and RFC 6238 test vectors.
PRs welcome! Please open an issue first for major changes.
totp · hotp · 2fa · mfa · two-factor authentication · multi-factor authentication
google authenticator · microsoft authenticator · authy · 1password · bitwarden
one-time password · rfc 6238 · rfc 4226 · otpauth · qr code · authentication
security · nodejs · typescript · deno · bun · cloudflare workers · edge
zero dependencies · lightweight · tiny · esm · web crypto · hmac
⭐ If you find this useful, please star the repo on GitHub! ⭐
Download counts and popularity will appear here as the collector receives them.
Declarations describe requirements; they are not a resolved dependency graph.
Source distribution
{
"integrity": "sha512-SgimcKfpdOG85pibehvpXDxbXD4QxTfxCzm0kj08z4xTQR6cjlO7z9OU6pLDPIGVPmcuXZs9NIaYUjsyzBVlgQ==",
"shasum": "f09779aef30e24d4e7459e93f3477c892945dd9e",
"tarball": "https://registry.npmjs.org/2fa-lib/-/2fa-lib-0.1.2.tgz",
"fileCount": 6,
"unpackedSize": 30204,
"signatures": [
{
"keyid": "SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U",
"sig": "MEQCID3NmmlK5bIsF96/kM2aMjqqh4/2FJmQoLPf4C8i62S4AiA+ws6OlRAfCSJaLhmMD63vYQWDRlQkeV9FUeqRdpSUxw=="
}
]
}Observed since 20 Oct, 19:12 UTC
No superseded metadata states collected. Unchanged observations do not create new revisions.
package_version:a4f7345915609eb57b9fbfec5de37ea968387b0b7a06eb11c26fc859fc83a376