npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@sourceregistry/node-totp

v2.0.0

Published

Node.js TOTP library for generating otpauth URIs and verifying time-based one-time passwords

Readme

@sourceregistry/node-totp

npm version JSR License Build Status Coverage

A zero-dependency TOTP (Time-based One-Time Password) library for Node.js. It implements the RFC 4226/6238 code calculation and generates otpauth:// setup URIs. Full verifier compliance requires application-managed replay protection, attempt throttling, and secure per-account secret storage.

  • RFC 4226/6238 code calculation, with an API for replay-safe verification
  • Zero external dependencies
  • Timing attack resistant token comparison
  • TypeScript definitions included
  • Tested against RFC vectors
  • Strict input validation and safe defaults

Installation

npm install @sourceregistry/node-totp

Usage

Basic Example

import { generateURI, verifyToken } from '@sourceregistry/node-totp';

// Generate setup URI for authenticator apps
const { uri, secret } = generateURI({
  issuer: 'MyApp',
  account: '[email protected]',
  digits: 6,
  period: 30
});

console.log('Scan this URI in your authenticator app:', uri);
// otpauth://totp/MyApp:user%40example.com?issuer=MyApp&secret=...

// Later, check user input (stateless; see verifyTokenOnce for login flows)
const userInput = '123456';
const isValid = verifyToken(userInput, secret, {
  digits: 6,
  period: 30,
  window: 1 // Accept tokens from +/-30 seconds
});

console.log('Token valid:', isValid);

Advanced Configuration

import totp from '@sourceregistry/node-totp';

// Generate with custom secret length (algorithm-appropriate defaults)
const { secret } = totp.generateURI({
  issuer: 'SecureApp',
  account: '[email protected]',
  algorithm: 'SHA512', // Uses 64-byte secret by default
  byteLength: 48 // Override default secret length
});

// Verify with custom time (useful for testing)
const testTime = Math.floor(Date.now() / 1000);
const testToken = totp.generateToken(
  totp.base32.decode(secret),
  Math.floor(testTime / 30),
  6,
  'SHA512'
);

const isValid = totp.verifyToken(testToken, secret, {
  algorithm: 'SHA512',
  digits: 6,
  period: 30,
  window: 2,
  now: testTime // Use specific timestamp instead of Date.now()
});

API Reference

generateURI(options)

Generates an otpauth:// URI and a secret for TOTP setup.

  • issuer (string, required): Service name such as MyApp; cannot contain a colon or control characters
  • account (string, required): User identifier such as an email address or Alice Smith; cannot contain a colon or control characters
  • secret (string, optional): Canonical unpadded Base32 secret containing at least 16 decoded bytes
  • algorithm (string, optional): SHA1, SHA256, or SHA512 (default SHA1); casing is normalized at runtime
  • digits (number, optional): 6 or 8 for otpauth:// compatibility (default 6)
  • period (number, optional): Time step in seconds (default 30)
  • byteLength (number, optional): Generated secret length in bytes (minimum 16; defaults to 20, 32, or 64 for SHA1, SHA256, or SHA512)

Returns { uri: string, secret: string }.

verifyToken(token, secret, options?)

Checks a TOTP token against a secret without tracking prior successful uses. Use verifyTokenOnce for authentication.

  • token (string): User-provided token with 6 to 8 digits
  • secret (string): Canonical unpadded Base32 secret
  • options.window (number): Time steps to check on either side of the current step (0 to 2; default 1, meaning +/-30 seconds with the default period)
  • options.period (number): Time step in seconds (default 30)
  • options.algorithm (string): Hash algorithm (default SHA1)
  • options.digits (number): Expected token length (default 6)
  • options.now (number): Unix timestamp in seconds, useful for testing

Returns boolean.

verifyTokenOnce(token, secret, claimCounter, options?)

Checks a token and passes its matched time-step counter to claimCounter. Returns Promise<boolean> and accepts the token only when the callback returns true. It rejects secrets shorter than 16 bytes.

The callback must atomically advance a durable, per-account lastAcceptedCounter only if the matched counter is greater than the stored value. Return true only when the update succeeds. A database conditional update or transaction can provide this guarantee across concurrent requests and server instances. An in-memory set cannot protect a multi-instance deployment or survive a restart.

import { verifyTokenOnce } from '@sourceregistry/node-totp';

const accepted = await verifyTokenOnce(token, user.totpSecret, counter =>
  db.claimTotpCounter(user.id, counter) // App-defined atomic conditional update
);

options has the same shape and defaults as verifyToken. Rate-limit attempts per account before calling either verifier.

generateToken(secret, counter, digits, algorithm)

Generates a TOTP token.

  • secret (Buffer): Decoded secret buffer
  • counter (number): Time step counter
  • digits (number): Token length (6, 7, or 8)
  • algorithm (string): Hash algorithm (SHA1, SHA256, or SHA512)

Returns a zero-padded string token.

base32

RFC 4648 Base32 helpers:

  • base32.encode(buffer: Buffer): string
  • base32.decode(base32Str: string): Buffer

The library accepts canonical unpadded Base32 secrets. Padded or non-canonical forms are normalized or rejected before use.

Security Notes

  • Token comparison uses crypto.timingSafeEqual()
  • Supported algorithms are limited to SHA1, SHA256, and SHA512
  • The HOTP/TOTP calculation supports 6, 7, or 8 digits; setup URIs support 6 or 8 as specified by the published key URI format
  • Verification windows are limited to 0, 1, or 2 steps
  • Period must be a positive integer
  • New secrets must contain at least 16 bytes; generated secrets use algorithm-appropriate byte lengths by default
  • Supplied secrets must also be randomly generated; length alone does not make a predictable secret secure
  • The stateless verifyToken still accepts shorter existing secrets for migration; verifyTokenOnce requires at least 16 bytes
  • Give every enrolled account a distinct random secret and protect secrets in storage
  • Enforce attempt throttling across login sessions; the library has no account or attempt store
  • Use verifyTokenOnce with a durable atomic counter claim to prevent code reuse
  • Authenticator support for non-default algorithms and periods varies; test the chosen settings with the client apps you support

Testing

npm test
npm run test:coverage

Examples

QR Code Generation (with qrcode package)

import qrcode from 'qrcode';
import { generateURI } from '@sourceregistry/node-totp';

const { uri } = generateURI({
  issuer: 'MyApp',
  account: '[email protected]'
});

const qrDataUrl = await qrcode.toDataURL(uri);
// Display qrDataUrl in your HTML template

Atomic replay claim

For a SQL-backed application, db.claimTotpCounter(userId, counter) can run a conditional update and return true only when one row changed:

UPDATE user_totp
SET last_accepted_counter = :counter
WHERE user_id = :user_id
  AND (last_accepted_counter IS NULL OR last_accepted_counter < :counter);

Keep the secret and last_accepted_counter per account. Enforce attempt throttling across sessions before calling verifyTokenOnce, and activate a new enrollment only after the user proves possession of the generated secret. Reset the stored counter atomically when rotating that account's secret.

License

Apache-2.0 © A.P.A. Slaa


Note: This library is designed for server-side Node.js applications. For browser usage, consider using a Web Crypto API compatible alternative.