Catalog npm
Back to packageRELEASE DETAILS

0.1.2

npm

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.

1password2faauthauthenticationauthenticatorauthybrowserbuncloudflare-workersdenoedgeesmgoogle-authenticatorhmachmac-otphotp
All keywords (45)
lightweightloginmfamulti-factormulti-factor-authenticationnodeone-time-passcodeone-time-passwordotpotpauthqr-codeqrcoderfc-4226rfc-6238rfc4226rfc6238securitytime-basedtime-based-otptinytotptotp-generatortotp-verifytwo-factortwo-factor-authenticationtypescriptvercel-edgeverifyzero-dependencies
Published8 Apr 2026
Direct dependencies0
Transitive dependenciesNot calculated

README

Current package documentation

2fa-lib

Tiny, zero-dependency TOTP & HOTP library 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.

npm version npm downloads bundle size license types github stars

Install • Quick Start • API • Examples • Compatibility • Why 2fa-lib?


✨ Features

  • 🪶 Tiny — under 5 KB minified, single file
  • 🚫 Zero dependencies — uses native Web Crypto API
  • 🌍 Universal — Node.js 18+, browsers, Deno, Bun, Cloudflare Workers, Vercel Edge, Netlify Edge
  • 🔒 Secure — constant-time comparison, cryptographically secure secrets
  • 📘 TypeScript — full type definitions included, no @types package needed
  • ✅ RFC compliant — RFC 6238 (TOTP) and RFC 4226 (HOTP), tested against official vectors
  • 📱 Universal app support — Google Authenticator, Microsoft Authenticator, Authy, 1Password, Bitwarden, Duo, FreeOTP
  • 🛡️ Built-in security helpers — rate limiter, replay guard, hashed backup codes
  • 🔄 QR code URI — generates otpauth:// URIs for any QR library
  • 🎮 Steam Guard — Steam's 5-character format supported
  • 📥 Import from QR — parse otpauth:// URIs from other apps
  • 💎 ESM + CJS — works with import and require
  • 🆕 Backup codes — generate + securely hash recovery codes

📦 Installation

npm install 2fa-lib
yarn add 2fa-lib
pnpm add 2fa-lib
bun add 2fa-lib

🚀 Quick Start

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');
}

📘 TypeScript

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');

📱 Authenticator App Compatibility

AppStatusNotes
Google Authenticator✅ FullAll algorithms supported
Microsoft Authenticator✅ FullUse microsoftAuthenticatorURI() (forces SHA1/6/30)
Authy✅ FullAll algorithms supported
1Password✅ FullAll algorithms supported
Bitwarden✅ FullAll algorithms supported
Duo Mobile✅ FullStandard TOTP
FreeOTP / FreeOTP+✅ FullAll algorithms supported
Steam Guard✅ FullUse steamTOTP() for 5-char codes

⚠️ Microsoft Authenticator silently ignores algorithm, digits, and period URI parameters — it always uses SHA1, 6 digits, 30 seconds. Use the dedicated microsoftAuthenticatorURI() helper to guarantee compatibility.


📚 API

Core

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);

QR Code Provisioning

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? }

Security Helpers

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');

Steam Guard

steamTOTP(secret, options?)

Generate a 5-character Steam Guard code.

const code = await steamTOTP(secret); // → "K8R3F"

💡 Examples

Express.js — Enable 2FA for a user

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 });
});

Next.js App Router — Login with 2FA

// 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) });
}

Cloudflare Workers — Edge runtime

import { totp, verify } from '2fa-lib';

export default {
  async fetch(request, env) {
    const code = await totp(env.USER_SECRET);
    return new Response(code);
  },
};

🤔 Why 2fa-lib?

Feature2fa-libotplibspeakeasyotpauth
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

🔐 Security Best Practices

  1. Encrypt secrets at rest. Never store TOTP secrets in plaintext in your database.
  2. Use the rate limiter. Brute-forcing 6 digits takes ~1M attempts on average — enforce limits.
  3. Use the replay guard. Don't allow the same code to be used twice.
  4. Hash backup codes. Never store them in plaintext.
  5. Use HTTPS. Always.
  6. Allow drift. Use window: 1 (±30s) to handle clock skew between server and phone.

🧪 Testing

npm test

The library is tested against the official RFC 4226 and RFC 6238 test vectors.


📖 Standards


🤝 Contributing

PRs welcome! Please open an issue first for major changes.


📄 License

MIT © ai-mehedi


Keywords

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! ⭐

Downloads & popularity

Awaiting observations

No metric observations yet

Download counts and popularity will appear here as the collector receives them.

Declared dependencies 0

Native requirements
0 direct targetsNot calculated transitive count
No declared dependencies in this collected response

Declarations describe requirements; they are not a resolved dependency graph.

Distribution files

1 collected
Distribution 1

Source distribution

Source file
Checksums from source
{
  "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=="
    }
  ]
}

Repository

PENDING
ai-mehedi/2fa-lib

Shared repository data · GitHub

License detected in repository
Not collected

Metadata history

Meaningful changes
Current collected state Current

Observed since 20 Oct, 19:12 UTC

No superseded metadata states collected. Unchanged observations do not create new revisions.

Record identity

package_version:a4f7345915609eb57b9fbfec5de37ea968387b0b7a06eb11c26fc859fc83a376

Other collected versions

0.1.08 AprView all collected versions →