π A zero-dependency, isomorphic TypeScript module for generating and validating HOTP and TOTP codes, compliant with RFC 4226 and RFC 6238. ποΈ
Try it out on JSFiddle: Live Demo
- Zero Dependencies: No external libraries needed. Works out-of-the-box.
- Isomorphic: Runs seamlessly in Node.js, Deno, Bun, and modern browsers.
- Standard Compliant: Strictly follows RFC 4226 (HOTP) and RFC 6238 (TOTP).
- Secure Validation: Includes a timing-safe validation function with a
windowto handle clock drift. - TypeScript Native: Written in TypeScript with full type definitions included.
npm install one-time-passUse the library directly in an HTML file via a CDN.
<script
src="https://cdn.jsdelivr.net/npm/one-time-pass/dist/index.umd.js"
></script>
<script>
(async () => {
// The library is available on the `window.otp` object
const secret = await window.otp.generateSecret();
console.log("Secret:", secret);
const token = await window.otp.generateTOTP(secret);
console.log("Token:", token);
})();
</script>The library provides a simple and modern async API.
First, generate a secure, Base32-encoded secret key for your user. This should be stored safely.
import { generateSecret } from "one-time-pass";
const secret = await generateSecret();
// Example Output: 'JBSWY3DPEHPK3PXPJBSWY3DPEHPK3PXP'
console.log("Your new secret is:", secret);Use the secret key to generate a time-based token.
import { generateTOTP } from "one-time-pass";
const tokenWithOptions = await generateTOTP(secret, {
algorithm: "SHA-1",
digits: 6,
period: 30, // 30 seconds
epoch: Date.now(),
});Validate the token submitted by the user. The window option allows you to
accept tokens from adjacent time steps to account for clock drift.
import { validate } from "one-time-pass";
const userToken = "287082"; // Token from user input
// Check the current, previous, and next time windows (window: 1)
const delta = await validate(userToken, secret, { window: 1 });
if (delta !== null) {
console.log("β
Token is valid!");
console.log(`Matched with delta: ${delta}`); // Can be -1, 0, or 1
} else {
console.log("β Token is invalid.");
}You can also generate counter-based tokens. Ensure you securely store and increment the counter for each use.
import { generateHOTP } from "one-time-pass";
const hotpToken = await generateHOTP("secret", {
counter: 1,
algorithm: "SHA-1",
digits: 6,
});
console.log("Your HOTP token is:", hotpToken);| Function | Parameters | Returns | Description |
|---|---|---|---|
generateSecret |
(length?: number) |
Promise<string> |
Creates a new Base32 secret key. |
generateTOTP |
(secret: string, options?: TOTPOptions) |
Promise<string> |
Generates a time-based OTP. |
generateHOTP |
(secret: string, options?:HOTPOptions) |
Promise<string> |
Generates a counter-based OTP. |
validate |
(token: string, secret: string, options?: TOTPValidateOptions) |
Promise<number | null> |
Validates a TOTP token and returns the matched delta or null. |
type HmacAlgorithm = "SHA-1" | "SHA-256" | "SHA-512";
type HOTPOptions = {
algorithm?: HmacAlgorithm;
digits?: number; // default: 6
counter?: number;
};
type TOTPOptions = {
algorithm?: HmacAlgorithm;
period?: number; // seconds, default: 30
digits?: number; // default: 6
epoch?: number; // ms, default: Date.now()
};
type TOTPValidateOptions = TOTPOptions & {
window?: number;
};The TOTP (Time-Based One-Time Password) validation includes a time window tolerance mechanism to handle clock drift between the client and server. Since TOTP tokens are time-sensitive, even small differences in system clocks can cause valid tokens to be rejected.
The window parameter defines how many time steps forward and backward the
validator will check when validating a token. For example:
- Window = 1: Checks 3 time periods (previous, current, next)
The validation function returns a delta value indicating the time synchronization status:
| Delta | Meaning | Description |
|---|---|---|
0 |
Perfect sync | Token matches current time period |
-1 |
Client behind | Token matches previous time period |
1 |
Client ahead | Token matches next time period |
null |
Invalid token | No match found within the allowed window |
- Default window of 1 provides a good balance between usability and security
- Larger windows increase the attack surface and should be used cautiously
- The delta information can be used for monitoring clock drift across clients
- Consider implementing rate limiting to prevent brute force attacks
Pull requests are welcome! If you have suggestions for improvements or find any issues, please feel free to open an issue or create a pull request.
This project is licensed under the MIR License. See the LICENSE file for details.