Authentication · Open source / Public project
typed-totp
One-time passwords. Small interfaces. Explicit security boundaries.
- 01Public API
- 02TOTP / HOTP
- 03Injectable ports
- 04Web Crypto
The problem
Generate and verify time-based and counter-based passwords without tying core logic to a particular clock or cryptographic backend.
Requirements
Correct HOTP and TOTP output, a testable clock, no runtime dependencies, and an API usable both as plain functions and as a configured instance.
System & architecture
A functional API wraps application services. TOTP delegates to HOTP; pure counter and formatting functions sit behind injectable clock, HMAC, encoding, and comparison ports.
Engineering decisions
Use platform Web Crypto and zero runtime dependencies. Keep I/O behind interfaces for deterministic tests, while leaving pure mathematics as simple functions. A functional API makes adoption easy; an object API supports reusable configuration.
Boundaries & trade-offs
An OTP library is one part of MFA. Applications must still implement replay protection, rate limiting, encrypted secret storage, enrollment, and recovery. ESM and Web Crypto requirements constrain older runtimes.
Evidence to inspect
The repository documents independent HOTP/TOTP tests against RFC vectors, fixed-clock testing, typed errors, and a security guide. These are repository features, not a claim of an independent security audit.
Testing
HOTP and TOTP are tested independently against published RFC vectors. Injecting a fixed clock makes time-dependent behaviour deterministic rather than flaky, and typed errors are asserted rather than matched on message text.
Lessons learned
Choosing which parts of a cryptographic library are pure functions and which are ports decides how testable the whole thing is. Deciding that before writing the API was worth more than any later refactor.
Constraints
ESM-only with platform Web Crypto and no runtime dependencies. That rules out older runtimes and any Node-specific cryptographic API, and it was a deliberate trade for a library that should run unchanged in a browser, a worker, and on a server.
Security
Comparison goes through a dedicated port so verification can be constant-time rather than short-circuiting on the first differing digit. The drift window is bounded and explicit instead of open-ended. The documented boundary matters as much: replay protection, rate limiting, encrypted secret storage, enrollment, and recovery belong to the application, not the library.
What I would improve
Ship recovery-code helpers and enrollment guidance so the surrounding MFA flow is less left to the caller, and publish a worked example of secret storage rather than describing it.
Source: public repository README, reviewed September 2026. Repository tests and deployment claims have not been independently audited here.