npm package: 2fa-lib
Description: 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.
Author: ai-mehedi
Latest version: 0.1.2

<div align="center">

# 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](https://img.shields.io/npm/v/2fa-lib.svg?style=flat-square)](https://www.npmjs.com/package/2fa-lib)
[![npm downloads](https://img.shields.io/npm/dm/2fa-lib.svg?style=flat-square)](https://www.npmjs.com/package/2fa-lib)
[![bundle size](https://img.shields.io/bundlephobia/minzip/2fa-lib?style=flat-square)](https://bundlephobia.com/package/2fa-lib)
[![license](https://img.shields.io/npm/l/2fa-lib.svg?style=flat-square)](./LICENSE)
[![types](https://img.shields.io/npm/types/2fa-lib.svg?style=flat-square)](./src/index.d.ts)
[![github stars](https://img.shields.io/github/stars/ai-mehedi/2fa-lib?style=flat-square)](https://github.com/ai-mehedi/2fa-lib)

[Install](#-installation) • [Quick Start](#-quick-start) • [API](#-api) • [Examples](#-examples) • [Compatibility](#-authenticator-app-compatibility) • [Why 2fa-lib?](#-why-2fa-lib)

</div>

---

## ✨ 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

```bash
npm install 2fa-lib
```

```bash
yarn add 2fa-lib
```

```bash
pnpm add 2fa-lib
```

```bash
bun add 2fa-lib
```

---

## 🚀 Quick Start

```js
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.

```ts
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

| 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`, 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).

```js
await totp('JBSWY3DPEHPK3PXP');
await totp(secret, { digits: 8, period: 60, algorithm: 'SHA256' });
```

#### `hotp(secret, counter, options?)`
Generate an HMAC-based one-time password (HOTP).

```js
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).

```js
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.

```js
const secret = generateSecret(); // → "JBSWY3DPEHPK3PXP..."
```

#### `timeRemaining(options?)`
Seconds remaining in the current TOTP step. Useful for countdown UIs.

```js
console.log(`${timeRemaining()}s until next code`);
```

#### `totpPair(secret, options?)`
Returns the current and next TOTP codes — useful for "next code" UI hints.

```js
const { current, next } = await totpPair(secret);
```

### QR Code Provisioning

#### `buildURI(options)`
Build an `otpauth://` URI for QR code provisioning.

```js
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**.

```js
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.

```js
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).

```js
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.

```js
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.

```js
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.

```js
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()`.

```js
if (!isValidTokenFormat(input)) return res.status(400).send('Bad format');
```

### Steam Guard

#### `steamTOTP(secret, options?)`
Generate a 5-character Steam Guard code.

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

---

## 💡 Examples

### Express.js — Enable 2FA for a user

```js
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

```ts
// 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

```js
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?

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

---

## 🔐 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

```bash
npm test
```

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

---

## 📖 Standards

- [RFC 6238](https://datatracker.ietf.org/doc/html/rfc6238) — Time-based One-Time Password (TOTP)
- [RFC 4226](https://datatracker.ietf.org/doc/html/rfc4226) — HMAC-based One-Time Password (HOTP)
- [RFC 4648](https://datatracker.ietf.org/doc/html/rfc4648) — Base32 encoding
- [Key URI Format](https://github.com/google/google-authenticator/wiki/Key-Uri-Format) — Google Authenticator

---

## 🤝 Contributing

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

---

## 📄 License

[MIT](./LICENSE) © [ai-mehedi](https://github.com/ai-mehedi)

---

<div align="center">

### 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](https://github.com/ai-mehedi/2fa-lib)!** ⭐

</div>