otplib

repository·main·Indexed 25 days ago

https://github.com/yeojz/otplib

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

Tokens
68.3K
Snippets
230
Records
377
Agent score
81%

What's inside otplib

  1. Overview of otplib

    main
    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.
  2. What is benchmarked in otplib

    main

    The 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, and truncateDigits.
    • URI: Parsing and generating URIs, including generateTOTP / generateHOTP helpers and roundtrips.
  3. Breaking Change: Async API and Sync Variants

    main

    All generate() and verify() functions are now async by default. If you require synchronous behavior, you must use the Sync variants (e.g., generateSync).

    // v12 (synchronous)
    const token = authenticator.generate(secret);
    
    // v13 (async)
    const token = await generate({ secret });
    
    // v13 (sync)
    const token = generateSync({ secret });
  4. When to use @otplib/plugin-crypto-noble

    main

    Use the @otplib/plugin-crypto-noble plugin 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/hashes library.
    • 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.
  5. Key features of otplib

    main

    otplib 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.
  6. Use the Result pattern for safe execution

    main

    To avoid try/catch blocks when handling potential errors (like SecretTooShortError), use wrapResult for synchronous functions or wrapResultAsync for asynchronous functions. These return an OTPResult object where you can check the ok property.

    // 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);
    }
  7. What properties are covered by fuzz testing

    main

    The fuzz testing suite in otplib validates several critical properties to ensure library reliability and security:

    • Invariants:
      • Round-tripping: Ensures that decode(encode(x)) always returns x.
      • Determinism: Ensures functions produce identical outputs for identical inputs.
    • Robustness: Verifies the library does not crash or hang on "garbage" input (random strings, massive buffers, etc.) and fails gracefully with expected errors like TokenFormatError instead of internal errors like TypeError or RangeError.
    • 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.
  8. How @otplib/plugin-base32-alt works

    main

    By default, otplib treats 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 Base32DecodeError or Base32EncodeError. The actual cause of the error is available in the error's cause property.
  9. Prevent replay attacks in HOTP and TOTP

    main

    otplib is 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.
  10. Understand @otplib/v12-adapter limitations

    main

    While 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, and hotp singleton instances and their classes. It does not export specific utility functions that were previously imported directly from otplib/core or 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 crypto module to work seamlessly.
  11. Hooks vs Plugins: When to use which

    main

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

  12. Understand the otpauth:// URI format

    main

    The @otplib/uri package works with the standard otpauth:// URI format used for provisioning OTP accounts (often via QR codes).

    Format: otpauth://TYPE/LABEL?PARAMETERS

    • TYPE: totp or hotp
    • LABEL: issuer:account or just account
    • PARAMETERS: secret, issuer, algorithm, digits, period/counter

    Example: otpauth://totp/GitHub:user@example.com?secret=GEZDGNBVGY3TQOJQGEZDGNBVGY&issuer=GitHub