Migration (v12 Adapter)
This adapter mimics the v12 synchronous API while using v13's plugins under the hood. However, some fundamental changes from v13 may carry over.
Recommendation
This adapter is intended as a temporary bridge. You are still recommended to do a full migration to use v13 directly for full compatibility and future-proofing.
Class/Instance API Only
This adapter only exports the authenticator, totp, and hotp singleton instances and their classes. If you were importing specific utility functions directly from otplib/core or other internal paths in v12, those are not covered by this adapter.
Overview of Changes
v13 introduces a complete rewrite with the following major changes:
- Plugin-based architecture - Crypto and Base32 implementations are now pluggable
- Async-first API - All token generation and verification is now async
- TypeScript-first - Full type safety with comprehensive type definitions
- Modular packages - Use only what you need with scoped packages
- Functional-First API - Functional API is now recommended over Class API for tree-shaking and simplicity
Quick Migration
Using the v12 Adapter (Drop-in Replacement)
If you want to upgrade to v13 internals but keep your existing v12 code working without changes, use the @otplib/v12-adapter package.
npm install @otplib/v12-adapterThen update your imports:
// v12
import { authenticator } from "otplib";
// v13 with Adapter
import { authenticator } from "@otplib/v12-adapter";Full Migration
Using the Main Package
If you want minimal changes, use the otplib package which includes default plugins:
// v12
import { authenticator } from "otplib";
const secret = authenticator.generateSecret();
const token = authenticator.generate(secret);
const isValid = authenticator.verify({ token, secret });// v13 - Using the Functional API (Recommended)
import { generateSecret, generate, verify } from "otplib";
const secret = generateSecret();
const token = await generate({ secret });
const result = await verify({ secret, token });
const isValid = result.valid;
// v13 - Using the OTP class
import { OTP } from "otplib";
const otp = new OTP();
const secret = otp.generateSecret();
const token = await otp.generate({ secret });
const result = await otp.verify({ secret, token });
const isValid = result.valid;Using Individual Packages
// v13 with explicit plugins
import { generate, verify } from "@otplib/totp";
import { crypto } from "@otplib/plugin-crypto-noble";
import { base32 } from "@otplib/plugin-base32-scure";
const token = await generate({
secret: "GEZDGNBVGY3TQOJQGEZDGNBVGY",
crypto,
base32,
});
const result = await verify({
secret: "GEZDGNBVGY3TQOJQGEZDGNBVGY",
token,
crypto,
base32,
});Breaking Changes
1. Async API
All generate() and verify() functions are now async by default. You may use the sync variants if you need synchronous behavior.
// v12 (synchronous)
const token = authenticator.generate(secret);
// v13 (async)
const token = await generate({ secret });
// v13 (sync)
const token = generateSync({ secret });2. Plugin Injection Required
v13 requires explicit crypto and base32 plugins (except when using the otplib package):
// v13 - must provide plugins
import { generate } from "@otplib/hotp";
import { crypto } from "@otplib/plugin-crypto-node";
import { base32 } from "@otplib/plugin-base32-scure";
const token = await generate({
secret: "GEZDGNBVGY3TQOJQGEZDGNBVGY",
counter: 0,
crypto,
base32,
});3. Options Object Pattern
Functions now take a single options object instead of positional arguments:
// v12
authenticator.generate(secret);
authenticator.verify({ token, secret });
// v13
generate({ secret, crypto, base32 });
verify({ secret, token, crypto, base32 });4. Verify Returns Object
verify() now returns a result object instead of a boolean:
// v12
const isValid = authenticator.verify({ token, secret }); // boolean
// v13
const result = await verify({ secret, token }); // { valid: boolean; delta?: number; epoch?: number }
const isValid = result.valid;
const delta = result.delta; // 0 = exact match, +/- for window offset
const epoch = result.epoch; // Exact epoch (in seconds) of the period that matched5. Secret Format Changes
v13 accepts both Base32 strings and raw Uint8Array:
// Base32 string (requires base32 plugin)
await generate({
secret: 'GEZDGNBVGY3TQOJQGEZDGNBVGY',
crypto,
base32, // required for string secrets
});
// Raw bytes (no base32 plugin needed)
await generate({
secret: new Uint8Array([...]),
crypto,
});6. Secret Length Guardrails
v13 enforces a minimum secret length of 16 bytes. Older deployments may have shorter secrets.
Legacy Secrets
If your existing secrets are shorter than 16 bytes, you may need to override guardrails for backward compatibility. This is not done by default to preserve security. See Danger Zone - Guardrails for details.
6. Class-Based API Changes
The class API has been updated:
// v12
import { authenticator } from "otplib";
authenticator.options = { digits: 8 };
const token = authenticator.generate(secret);
// v13 - Using the unified OTP class (new)
import { OTP } from "otplib";
const otp = new OTP({
strategy: "totp", // 'totp' or 'hotp'
digits: 8,
});
const token = await otp.generate({ secret: "GEZDGNBVGY3TQOJQGEZDGNBVGY" });
// v13 - Using strategy-specific classes
import { TOTP } from "otplib";
import { crypto } from "@otplib/plugin-crypto-noble";
import { base32 } from "@otplib/plugin-base32-scure";
const totp = new TOTP({
crypto,
base32,
digits: 8,
});
totp.secret = "GEZDGNBVGY3TQOJQGEZDGNBVGY";
const token = await totp.generate();7. Unified Strategy Support
v13.1 introduces a unified API that supports multiple strategies through a single interface:
import { OTP, generate, verify } from "otplib";
// Using the OTP class with different strategies
const totp = new OTP({ strategy: "totp" });
const hotp = new OTP({ strategy: "hotp" });
// Using functional API with strategy parameter
const token1 = await generate({ secret, strategy: "totp" });
const token2 = await generate({ secret, strategy: "hotp", counter: 0 });Package Mapping
| v12 Package | v13 Package(s) |
|---|---|
otplib | otplib (all-in-one) or individual packages |
otplib/authenticator | @otplib/totp |
otplib/hotp | @otplib/hotp |
otplib/totp | @otplib/totp |
| (built-in) | @otplib/plugin-crypto-node |
| (built-in) | @otplib/plugin-crypto-noble |
| (built-in) | @otplib/plugin-crypto-web |
| (built-in) | @otplib/plugin-base32-scure |
@otplib/core | @otplib/core |
Choosing Crypto Plugins
v13 offers three crypto plugins:
NodeCryptoPlugin (Node.js only)
import { crypto } from "@otplib/plugin-crypto-node";- Uses Node.js built-in
cryptomodule - Synchronous HMAC operations
- Best performance for Node.js applications
NobleCryptoPlugin (Universal)
import { crypto } from "@otplib/plugin-crypto-noble";- Pure JavaScript implementation using
@noble/hashes - Synchronous HMAC operations
- Works in Node.js, browsers, and edge runtimes
- Default in
otplibpackage
WebCryptoPlugin (Browsers)
import { crypto } from "@otplib/plugin-crypto-web";- Uses Web Crypto API
- Async HMAC operations (hardware accelerated)
- Best for browser environments with SubtleCrypto support
Error Handling
v13 introduces specific error types:
import {
OTPError,
SecretError,
SecretTooShortError,
TokenError,
TokenLengthError,
CryptoError,
} from "@otplib/core";
try {
await generate({ secret: "short", crypto, base32 });
} catch (error) {
if (error instanceof SecretTooShortError) {
console.log("Secret must be at least 16 bytes");
}
}URI Generation
// v12
const uri = authenticator.keyuri("user@example.com", "MyApp", secret);
// v13 - functional
import { generateURI } from "otplib";
const uri = generateURI({
issuer: "MyApp",
label: "user@example.com",
secret: "GEZDGNBVGY3TQOJQGEZDGNBVGY",
});
// v13 - class-based
const totp = new TOTP({
issuer: "MyApp",
label: "user@example.com",
secret: "GEZDGNBVGY3TQOJQGEZDGNBVGY",
crypto,
base32,
});
const uri = totp.toURI();TypeScript Changes
v13 provides comprehensive types:
import type {
CryptoPlugin,
Base32Plugin,
HashAlgorithm,
Digits,
VerifyResult,
HOTPOptions,
TOTPOptions,
} from "@otplib/core";
// HashAlgorithm = "sha1" | "sha256" | "sha512"
// Digits = 6 | 7 | 8
// VerifyResult = { valid: boolean; delta?: number; epoch?: number }Tolerance Changes
v13 replaces the ambiguous window option with explicit tolerance parameters.
- TOTP: Replace
window(steps) withepochTolerance(seconds). - HOTP: Replace
window(steps) withcounterTolerance(steps).
Migration Reference (TOTP)
v12 window | v13 epochTolerance (30s period) | Description |
|---|---|---|
0 | 0 | Exact match only |
1 | 30 | ±1 step (±30 seconds) |
[1, 0] | [30, 0] | Past 1 step (30s) only |
2 | 60 | ±2 steps (±60 seconds) |
See Verification Tolerance for migration formulas, examples, and recommended values.