otplib
repository·main·Indexed 25 days ago
https://github.com/yeojz/otplibA TypeScript-first library for implementing HOTP (RFC 4226) and TOTP (Authenticator) protocols. It supports multiple runtimes including Node.js, Bun, Deno, and Browsers via a flexible plugin system for crypto and Base32 encoding. The library provides a functional API, a class-based API, and a CLI tool (otplib-cli) for encrypted secret storage and stateless scripting.
What's inside otplib
- otplib is a TypeScript-first library designed for HOTP (RFC 4226) and TOTP (RFC 6238) authentication. It features a pluggable architecture that allows developers to swap cryptographic and Base32 implementations (e.g., using Web Crypto, Node crypto, or custom providers) and provides an async-first API with synchronous alternatives. It is compatible with standard authenticator apps like Google Authenticator, Microsoft Authenticator, and Authy.
What is benchmarked in otplib
mainThe benchmark suite monitors several critical performance areas to ensure the library remains lightweight and fast:
- HOTP & TOTP Generation: Speed of token generation across different hashing algorithms (SHA-1, SHA-256, SHA-512).
- Verification: The cost of
verify()with and without window look-ahead (tolerance). - Base32 Operations: Throughput of encoding and decoding secrets of various lengths.
- Core Utilities: Primitives on the hot path, including
constantTimeEqual,counterToBytes,dynamicTruncate, andtruncateDigits. - URI: Parsing and generating URIs, including
generateTOTP/generateHOTPhelpers and roundtrips.
Breaking Change: Async API and Sync Variants
mainAll
generate()andverify()functions are now async by default. If you require synchronous behavior, you must use theSyncvariants (e.g.,generateSync).// v12 (synchronous) const token = authenticator.generate(secret); // v13 (async) const token = await generate({ secret }); // v13 (sync) const token = generateSync({ secret });When to use @otplib/plugin-crypto-noble
mainUse the
@otplib/plugin-crypto-nobleplugin in the following scenarios:- Cross-platform compatibility: When your code must run in Node.js, browsers, and edge runtimes.
- Edge runtimes: When running in environments that do not fully support the Web Crypto API.
- Pure JavaScript requirement: When you need an implementation without native dependencies.
- Audited crypto: When you want to use the audited
@noble/hasheslibrary. - Isomorphic applications: When building apps that run on both server and client.
- Synchronous HMAC: When you need synchronous HMAC operations in environments without Node.js
crypto.
Key features of otplib
mainotplib provides the following core capabilities:
- TypeScript Native: Complete type definitions and an async-first API design.
- Pluggable Architecture: Ability to swap crypto and Base32 implementations via plugins (e.g.,
@otplib/plugin-crypto-node,@otplib/plugin-crypto-web, or custom implementations). - RFC Compliance: Full implementation of RFC 4226 (HOTP) and RFC 6238 (TOTP) specifications.
- Authenticator Compatibility: Works with common apps like Google, Microsoft, and Authy.
Use the Result pattern for safe execution
mainTo avoid
try/catchblocks when handling potential errors (likeSecretTooShortError), usewrapResultfor synchronous functions orwrapResultAsyncfor asynchronous functions. These return anOTPResultobject where you can check theokproperty.// Synchronous Example import { wrapResult, generateSync, OTPError } from "otplib"; const safeGenerate = wrapResult(generateSync); const result = safeGenerate({ secret: "too-short" }); if (result.ok) { console.log("Token:", result.value); } else { console.error("Failed:", result.error.message); } // Asynchronous Example import { wrapResultAsync, verify } from "otplib"; const safeVerify = wrapResultAsync(verify); const result = await safeVerify({ token: "123456", secret: "GEZDGNBVGY3TQOJQGEZDGNBVGY", }); if (result.ok) { if (result.value.valid) { console.log("Valid!"); } } else { console.error("Verification failed:", result.error); }What properties are covered by fuzz testing
mainThe fuzz testing suite in
otplibvalidates several critical properties to ensure library reliability and security:- Invariants:
- Round-tripping: Ensures that
decode(encode(x))always returnsx. - Determinism: Ensures functions produce identical outputs for identical inputs.
- Round-tripping: Ensures that
- Robustness: Verifies the library does not crash or hang on "garbage" input (random strings, massive buffers, etc.) and fails gracefully with expected errors like
TokenFormatErrorinstead of internal errors likeTypeErrororRangeError. - Security Boundaries: Validates that different algorithms (e.g., SHA1 vs SHA256) or different counters/epochs produce distinct tokens.
- Consistency: Ensures different crypto implementations (e.g., Node vs Noble) produce identical results for the same inputs.
- Invariants:
How @otplib/plugin-base32-alt works
mainBy default,
otplibtreats string secrets as Base32 encoded (per RFC 4648) because that is what authenticator apps and OTPAuth URIs expect.This plugin allows you to bypass the Base32 requirement by providing a plugin that converts raw strings (like passphrases, hex, or base64) directly into bytes.
Important Notes:
- OTPAuth URIs: Generating OTPAuth URIs still requires Base32 secrets.
- Error Handling: Errors are still surfaced as
Base32DecodeErrororBase32EncodeError. The actual cause of the error is available in the error'scauseproperty.
Prevent replay attacks in HOTP and TOTP
mainotplibis a stateless library and does not track which tokens have already been verified. You must implement protection in your application logic:- For HOTP: Always increment the counter after a successful verification to ensure the same counter value cannot be reused.
- For TOTP: Implement stateful tracking in your application's database or cache. Without this, a valid token can be reused multiple times within its validity window.
Understand @otplib/v12-adapter limitations
mainWhile the adapter provides a synchronous-looking API, there are important constraints to be aware of:
- Class/Instance API Only: The adapter only exports the
authenticator,totp, andhotpsingleton instances and their classes. It does not export specific utility functions that were previously imported directly fromotplib/coreor other internal paths in v12. - Sync/Async Behavior: The adapter mimics the synchronous v12 API, but because it uses v13 plugins internally, it is designed for standard Node.js usage with the default
cryptomodule to work seamlessly.
- Class/Instance API Only: The adapter only exports the
Hooks vs Plugins: When to use which
mainIt is important to distinguish between Hooks and Plugins in
otplib:| Aspect | Plugins | Hooks | | :--- | :--- | | Purpose | Swap infrastructure (crypto backend, Base32 lib) | Customise OTP behaviour (token format) | | Scope | Affects HMAC computation or secret encoding | Affects token encoding and validation only | | Examples |
plugin-crypto-node,plugin-base32-scure| Steam Guard encoding, custom alphabets | | Required | Yes (crypto plugin is mandatory) | No (defaults to RFC 4226 numeric encoding) |Understand the otpauth:// URI format
mainThe
@otplib/uripackage works with the standardotpauth://URI format used for provisioning OTP accounts (often via QR codes).Format:
otpauth://TYPE/LABEL?PARAMETERS- TYPE:
totporhotp - LABEL:
issuer:accountor justaccount - PARAMETERS:
secret,issuer,algorithm,digits,period/counter
Example:
otpauth://totp/GitHub:user@example.com?secret=GEZDGNBVGY3TQOJQGEZDGNBVGY&issuer=GitHub- TYPE: