hectorm/otpauth for Node.js, Deno, Bun, and browser authentication
One Time Password (HOTP/TOTP) library for Node.js, Deno, Bun and browsers.
At a glance
- What is it?
- OTPAuth is a JavaScript library for generating and validating RFC 4226 HOTP and RFC 6238 TOTP credentials across server and browser runtimes. It offers standard, slim, and bare builds, but remains focused on OTP computation rather than complete MFA enrollment or login flows.
- Who is it for?
- Application developers should adopt OTPAuth when they need direct HOTP or TOTP generation and validation in JavaScript, especially across multiple supported runtimes. It is a poor fit for teams seeking a complete identity provider, credential storage system, QR-code renderer, or server login policy engine.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 5 days ago.
- What is it written in?
- Mainly JavaScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
OTPAuth's place in TOTP and HOTP authentication
OTPAuth addresses the narrow computation layer required by password-based multifactor authentication. It can generate and validate HMAC-Based One-Time Passwords defined by RFC 4226 and Time-Based One-Time Passwords defined by RFC 6238. The intended users are application developers working in Node.js, Deno, Bun, or browsers who need to create authenticator secrets, calculate current codes, check submitted codes, inspect time windows, and exchange credentials through Google Authenticator key URIs.
The library does not present itself as an identity platform or a complete MFA service. Its documented operations do not include storing users, registering authenticator devices, rendering QR codes, sending recovery codes, or implementing an HTTP login endpoint. Those tasks remain with the host application and its surrounding authentication system. This boundary matters because a correct token implementation can still be placed in an insecure login flow if enrollment secrets, rate limits, recovery paths, and session controls are handled poorly.
Both standards are supported, but the README uses TOTP for its primary example. HOTP support broadens the package's utility for counter-based workflows, while TOTP remains the path shown for Google Authenticator-compatible enrollment. OTPAuth is therefore most relevant when an application already owns the account and enrollment model but needs a small JavaScript layer for OTP standards.
How OTPAuth derives and checks time-based tokens
A TOTP object receives an issuer, account label, HMAC algorithm, token length, time period, and shared secret. The documented example uses `SHA1`, six digits, a 30-second period, and a base32 secret. If the secret is omitted, the documentation states that the library generates a cryptographically secure random secret. It also permits an `OTPAuth.Secret` object, including one created with a selected byte size.
Token generation is tied to time. Calling `totp.counter()` exposes the number of complete intervals since the Unix epoch, while `totp.remaining()` returns the milliseconds before the current token changes. These operations are useful to an interface that counts down to the next code or to server-side logic concerned with reuse within the current validity period. The core library computes the code; displaying a countdown and persisting the counter belong to the caller.
Validation uses a small search window. In the README example, `window: 1` checks the supplied token against the current time step and one adjacent step. A successful validation returns a token delta, while failure returns `null`. This is a clock-skew concession, not unlimited tolerance. The documentation explicitly recommends keeping the window as small as possible and implementing server-side throttling, because a broad search window increases the number of candidate values available to an attacker.
The documented defaults are described as the values used by most services. Developers should retain the issuer's required algorithm, digit count, and period rather than changing them for presentation consistency. Changing one of these parameters changes the generated credential and can cause a previously enrolled authenticator to disagree with the server.
Choosing OTPAuth's standard, slim, and bare distributions
Package exports expose three distinct distribution choices. The main `otpauth` import is used in the Node.js and Bun example. The `otpauth/slim` entry point is described as a slim build without bundled dependencies. The `otpauth/bare` entry point contains no bundled crypto and requires a custom HMAC function.
That bare build is the most explicit alternative to the default package. Instead of accepting the HMAC implementation supplied by the default build, the caller passes a function with the signature `(algorithm, key, message) => Uint8Array`. This can suit an environment where the host application requires control over cryptographic primitives, but it also transfers a security-sensitive responsibility to that application. The project does not document in the README how to validate a custom implementation beyond the required callback shape.
For Deno, the package is published through JSR as `@hectorm/otpauth`. Browser examples use jsDelivr and offer ESM import maps or a UMD script. Both browser forms include placeholders for a version and integrity hash, so the examples are templates rather than directly deployable markup until those values are populated. Package metadata also contains separate Node.js, Bun, Deno, and default export paths, allowing package-aware tooling to select an appropriate artifact.
This distribution design is a practical reason to consider OTPAuth in mixed JavaScript environments. It does, however, increase the number of build decisions an adopter must understand. Selecting `/bare` merely to reduce bundle size would move HMAC responsibility into the application without solving the problem.
A first TOTP use with documented examples
The README does not provide an installation command. For Node.js and Bun, it directs readers to the npm package and begins the usage example with an ESM import. Application developers should obtain the package through the npm page and keep the version used by their lockfile consistent with the version they intend to deploy.
After adding the package to an existing JavaScript project, the documented import is:
import * as OTPAuth from "otpauth";The next practical step is to construct a TOTP instance from the parameters required by the account service:
let totp = new OTPAuth.TOTP({
issuer: "ACME",
label: "Alice",
algorithm: "SHA1",
digits: 6,
period: 30,
secret: "US3WHSG7X5KAPV27VANWKQHF3SH3HULL",
});Do not treat the literal secret in this tutorial as an enrollment secret for a real account. It appears in the README as an example. For generated enrollment material, the documentation recommends at least 128 bits and shows a 20-byte random `OTPAuth.Secret`:
let secret = new OTPAuth.Secret({ size: 20 });Generate the current code and validate it within the narrow documented window:
let token = totp.generate();
let delta = totp.validate({ token, window: 1 });A valid match produces a delta. `null` means the token was not found within the configured search window. Applications should combine validation with server-side throttling. For Deno, the documented import changes to:
import * as OTPAuth from "jsr:@hectorm/otpauth";The README does not give a Deno installation command, so none should be inferred from the import example.
Enrollment through OTPAuth URI conversion
OTPAuth can convert a TOTP object to the Google Authenticator key URI format with `totp.toString()`. The documentation also exposes `OTPAuth.URI.stringify(totp)` for the same representation. The returned URI contains the issuer, account label, secret, algorithm, digit count, and period, allowing a separate QR-code library to present it to a user.
The distinction between URI generation and enrollment is important. OTPAuth constructs the data format that an authenticator expects, but the README explicitly places QR-code functionality outside the project's scope. Developers must choose a QR-code library, protect the enrollment route, deliver the QR code or URI through an appropriate channel, and handle the transition if a user loses access to the device.
For parsing existing enrollment data, `OTPAuth.URI.parse(uri)` converts a key URI back into a TOTP object. This is useful when an application receives authenticator configuration through some external provisioning process. It is not evidence that OTPAuth persists credentials. The README does not document secret storage, database integration, recovery policy, or credential revocation, so those behaviors cannot be attributed to the library.
The embedded secret in an OTPAuth URI is sensitive enrollment material. Anyone who obtains it may be able to generate compatible codes under the relevant service policy. A production implementation therefore needs to treat the URI, any QR representation, and stored enrollment records as secrets rather than ordinary profile data.
Validation windows, throttling, and replay limits
Clock drift is the central operational trade-off shown by the README. A validation window of `1` can accommodate modest disagreement between the client and server, but every additional step makes more codes potentially acceptable. The documentation therefore recommends the smallest practical window and explicitly calls for a server-side throttling mechanism.
The library returns a delta, which identifies where the accepted token was found. That return value can inform validation logic, but the README does not prescribe how long a server should remember it. For TOTP, `counter()` exposes the interval number and may help an application implement a check against token reuse during the same validity period. Actually storing and comparing that counter is application work.
There is no documented built-in account lockout, rate limiter, audit log, or distributed replay store. Those omissions are reasonable for a focused OTP library, but they prevent it from serving as the complete security boundary. A server that accepts a token after successful login should usually define whether that code can be submitted again during its remaining interval. The host system also needs throttling at a scope that resists repeated guessing, while avoiding rules that turn the service into an easy denial-of-service target.
The search window is therefore not a substitute for rate limiting. It handles bounded clock uncertainty. Throttling limits attempts. Reuse detection limits acceptance of a known code. These are separate controls.
OTPAuth maintenance, packaging, and license cost
The repository is not archived. Its last push was on 2026-10-12, and the listed releases include `v9.5.2` on 2026-09-03, `v9.5.1` on 2026-04-25, and `v9.5.0` on 2026-02-04. This supports a current maintenance and release cadence, although those dates alone do not establish compatibility with every consumer environment.
The package declares itself as an ES module and publishes type declarations under `dist/`. Its export map distinguishes import and require type declarations and selects environment-specific JavaScript artifacts for Bun, Deno, Node.js, and browsers. Consumers who pin a major version may need to review migration behavior when upgrading. The README excerpt does not document a formal deprecation schedule or long-term support policy, so an organization requiring a particular support window must verify that separately.
OTPAuth is distributed under the MIT License. The package metadata and README identify that license, so use and modification are governed by the repository's `LICENSE.md`. This review does not interpret the license or provide legal advice. Teams still need to review dependencies and deployment artifacts used alongside the library.
Upgrade work can also involve the selected entry point. A project using `otpauth`, `otpauth/slim`, or `otpauth/bare` should confirm that the new version preserves the expected export and HMAC arrangement. The lockfile is a concrete upgrade checkpoint because Node.js package resolution records the installed version and transitive state.
Editorial conclusion
Application developers should adopt OTPAuth when they need direct HOTP or TOTP generation and validation in JavaScript, especially across multiple supported runtimes. It is a poor fit for teams seeking a complete identity provider, credential storage system, QR-code renderer, or server login policy engine. Verify first that your target service accepts its algorithm, digit count, period, secret encoding, and URI format, then decide whether the bundled crypto, slim build, or bare build best fits the deployment.
Frequently asked questions
Which JavaScript runtimes support OTPAuth?
The README identifies Node.js, Deno, Bun, and browsers as supported environments. Package exports include environment-specific artifacts for Node.js, Bun, and Deno, while the browser documentation provides ESM and UMD examples through jsDelivr.
How do I generate and validate a TOTP token with OTPAuth?
Construct a `OTPAuth.TOTP` object with the required algorithm, digits, period, and secret, then call `totp.generate()` and pass the result to `totp.validate({ token, window: 1 })`. The README recommends keeping the window small and adding server-side throttling.
Does OTPAuth render QR codes or manage complete MFA enrollment?
No. It can convert a TOTP object to the Google Authenticator key URI format, but the README places QR-code generation outside the project. Storage, account enrollment, recovery, throttling, and the surrounding login flow remain application responsibilities.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/hectorm-otpauth)Was this useful?