Observed since 20 Oct, 19:12 UTC
No superseded metadata states collected. Unchanged observations do not create new revisions.
Easy 2-Factor Integration For Node.js
This library generates and verifies time-based one-time passwords (TOTP, RFC 6238) and HMAC-based one-time passwords (HOTP, RFC 4226) for two-factor authentication (2FA). It also creates the secret, the otpauth:// URI and the QR code that authenticator apps such as Google Authenticator or Authy scan.
otpauth:// URI and a QR code of that URI.npm install 2fa-node
Requires Node.js 20.19 or later.
import { generateSecret } from "2fa-node";
const { secret, uri, qr } = await generateSecret({
name: "MyApp", // issuer shown in the authenticator app
account: "user@example.com", // account shown in the authenticator app
});
console.log(secret); // store it for the user
console.log(uri); // otpauth://totp/MyApp:user%40example.com?secret=...&issuer=MyApp
console.log(qr); // PNG data URL to show to the user
import { generateSecret } from "2fa-node";
const { secret, uri, qr } = await generateSecret(
{ name: "MyApp", account: "user@example.com", counter: 0 },
"HOTP",
);
import { verifyToken } from "2fa-node";
const secret = "JBSWY3DPEHPK3PXP"; // the user's stored secret
const token = "123456"; // the token typed by the user
const isValid = verifyToken(secret, token);
console.log(isValid); // true if valid, false otherwise
import { verifyTokenOnce } from "2fa-node";
const secret = "JBSWY3DPEHPK3PXP"; // the user's stored secret
const token = "123456"; // the token typed by the user
const lastTimeStep = null; // the user's stored time step, null before the first login
const match = verifyTokenOnce(secret, token, lastTimeStep);
if (match) {
// Save match.timeStep as the user's lastTimeStep, so this token cannot be used again.
}
import { generateToken } from "2fa-node";
const secret = "JBSWY3DPEHPK3PXP";
const result = generateToken(secret);
console.log(result?.token); // the current token, or undefined if the secret is invalid
import { verifyHOTPToken } from "2fa-node";
const secret = "JBSWY3DPEHPK3PXP"; // the user's stored secret
const token = "123456"; // the token typed by the user
const counter = 1; // the user's stored counter
const isValid = verifyHOTPToken(secret, token, counter);
if (isValid) {
// Save counter + 1 so this token cannot be used again.
}
import { generateHOTPToken } from "2fa-node";
const secret = "JBSWY3DPEHPK3PXP";
const counter = 1;
const result = generateHOTPToken(secret, counter);
console.log(result?.token); // the token, or undefined if the secret is invalid
Tokens have 6 digits and use HMAC-SHA1; TOTP tokens change every 30 seconds. Secrets are case-insensitive base32 strings of at least 80 bits (16 characters).
generateSecret(options: SecretOptions, type: OtpType = "TOTP"): Promise<{ secret: string; uri: string; qr: string }>Generates a random secret for two-factor authentication.
options.name: issuer shown in the authenticator app, usually your product name.options.account: account shown in the authenticator app, usually the user's email.options.counter (optional): initial HOTP counter written to the URI. Defaults to 0; ignored for TOTP.options.numberOfSecretBytes (optional): size of the secret in bytes. Defaults to 20; must be an integer of at least 16, because RFC 4226 requires 128-bit secrets.type: "TOTP" (default) or "HOTP".secret: the base32 secret to store for the user.uri: the otpauth:// URI.qr: a PNG data URL of the URI's QR code.TypeError for an unknown type; RangeError for an invalid numberOfSecretBytes or HOTP counter.generateToken(secret: string): { token: string } | nullGenerates the current TOTP token.
{ token }, or null if the secret is invalid.verifyToken(secret: string, token?: string, window: number | [number, number] = 1): booleanVerifies a TOTP token.
window: how many 30-second steps before and after now are also accepted. Use [past, future] for different values; 0 accepts only the current step.true if the token is valid; false otherwise, including when the token is missing or malformed or the secret is invalid.RangeError if window is not made of non-negative integers or spans more than 98 steps in total.verifyTokenOnce(secret: string, token?: string, lastTimeStep?: number | null, window: number | [number, number] = 1): { timeStep: number } | nullVerifies a TOTP token like verifyToken, but also rejects tokens from lastTimeStep or earlier, so each token is accepted only once.
lastTimeStep: the timeStep returned by the user's last successful check, or null/undefined if there is none.window: same as in verifyToken.{ timeStep } with the 30-second step the token belongs to, or null if the token is rejected. Save timeStep for the user after each success.verifyToken, plus a RangeError if lastTimeStep is not a non-negative safe integer.generateHOTPToken(secret: string, counter: number = 0): { token: string } | nullGenerates the HOTP token for a counter.
{ token }, or null if the secret is invalid.RangeError if counter is not a non-negative safe integer.verifyHOTPToken(secret: string, token: string | undefined, counter: number): booleanVerifies an HOTP token against exactly the given counter.
true if the token is valid; false otherwise, including when the token is missing or malformed or the secret is invalid.RangeError if counter is not a non-negative safe integer.SecretOptions and OtpType ("TOTP" | "HOTP") are exported for TypeScript users.
verifyToken accepts the same token again while it is inside the window. To block replays, use verifyTokenOnce, including for the check that confirms enrollment, and save the returned timeStep only if it moves forward. Do it in a single statement and treat zero updated rows as a failed login, so two simultaneous requests with the same token cannot both succeed:
UPDATE users SET last_time_step = $step
WHERE id = $id AND (last_time_step IS NULL OR last_time_step < $step);
For HOTP, advance the counter the same way: UPDATE users SET counter = counter + 1 WHERE id = $id AND counter = $counter.
Limit how many verification attempts a user can make.
Keep secrets encrypted at rest.
verifyToken and verifyHOTPToken return false instead of null when the token is missing.verifyToken accepts one step (30 seconds) before and after now by default, instead of four.verifyHOTPToken requires the counter and no longer takes a window argument, which never had any effect.= padding or non-zero unused trailing bits, now make the generate functions return null and the verify functions return false.numberOfSecretBytes must be at least 16. Invalid types, counters and windows throw.issuer parameter instead of name.2fa-node/dist/secret.js no longer work.This project is licensed under the Mozilla Public License 2.0 - see the LICENSE.md file for details.
npm · 90 observations shown. Missing days are unknown.
| Date / source period | Value | Observed at |
|---|---|---|
| 2026-10-20 – 2026-10-20 | 0 | 2026-10-21T09:36:46.2338754Z |
| 2026-10-19 – 2026-10-19 | 0 | 2026-10-21T09:36:46.2338754Z |
| 2026-10-18 – 2026-10-18 | 62 | 2026-10-21T09:36:46.2338754Z |
| 2026-10-17 – 2026-10-17 | 104 | 2026-10-21T09:36:46.2338754Z |
| 2026-10-16 – 2026-10-16 | 55 | 2026-10-21T09:36:46.2338754Z |
| 2026-10-15 – 2026-10-15 | 0 | 2026-10-21T09:36:46.2338754Z |
| 2026-10-14 – 2026-10-14 | 0 | 2026-10-21T09:36:46.2338754Z |
| 2026-10-13 – 2026-10-13 | 0 | 2026-10-21T09:36:46.2338754Z |
| 2026-10-12 – 2026-10-12 | 63 | 2026-10-21T09:36:46.2338754Z |
| 2026-10-11 – 2026-10-11 | 36 | 2026-10-21T09:36:46.2338754Z |
| 2026-10-10 – 2026-10-10 | 87 | 2026-10-21T09:36:46.2338754Z |
| 2026-10-09 – 2026-10-09 | 146 | 2026-10-21T09:36:46.2338754Z |
| 2026-10-08 – 2026-10-08 | 236 | 2026-10-21T09:36:46.2338754Z |
| 2026-10-07 – 2026-10-07 | 120 | 2026-10-21T09:36:46.2338754Z |
| 2026-10-06 – 2026-10-06 | 0 | 2026-10-21T09:36:46.2338754Z |
| 2026-10-05 – 2026-10-05 | 278 | 2026-10-21T09:36:46.2338754Z |
| 2026-10-04 – 2026-10-04 | 156 | 2026-10-21T09:36:46.2338754Z |
| 2026-10-03 – 2026-10-03 | 100 | 2026-10-21T09:36:46.2338754Z |
| 2026-10-02 – 2026-10-02 | 132 | 2026-10-21T09:36:46.2338754Z |
| 2026-10-01 – 2026-10-01 | 211 | 2026-10-21T09:36:46.2338754Z |
| 2026-09-30 – 2026-09-30 | 250 | 2026-10-21T09:36:46.2338754Z |
| 2026-09-29 – 2026-09-29 | 168 | 2026-10-21T09:36:46.2338754Z |
| 2026-09-28 – 2026-09-28 | 220 | 2026-10-21T09:36:46.2338754Z |
| 2026-09-27 – 2026-09-27 | 68 | 2026-10-21T09:36:46.2338754Z |
| 2026-09-26 – 2026-09-26 | 325 | 2026-10-21T09:36:46.2338754Z |
| 2026-09-25 – 2026-09-25 | 193 | 2026-10-21T09:36:46.2338754Z |
| 2026-09-24 – 2026-09-24 | 172 | 2026-10-21T09:36:46.2338754Z |
| 2026-09-23 – 2026-09-23 | 186 | 2026-10-21T09:36:46.2338754Z |
| 2026-09-22 – 2026-09-22 | 70 | 2026-10-21T09:36:46.2338754Z |
| 2026-09-21 – 2026-09-21 | 91 | 2026-10-21T09:36:46.2338754Z |
| 2026-09-20 – 2026-09-20 | 96 | 2026-10-21T09:36:46.2338754Z |
| 2026-09-19 – 2026-09-19 | 5 | 2026-10-21T09:36:46.2338754Z |
| 2026-09-18 – 2026-09-18 | 88 | 2026-10-21T09:36:46.2338754Z |
| 2026-09-17 – 2026-09-17 | 185 | 2026-10-21T09:36:46.2338754Z |
| 2026-09-16 – 2026-09-16 | 92 | 2026-10-21T09:36:46.2338754Z |
| 2026-09-15 – 2026-09-15 | 0 | 2026-10-21T09:36:46.2338754Z |
| 2026-09-14 – 2026-09-14 | 75 | 2026-10-21T09:36:46.2338754Z |
| 2026-09-13 – 2026-09-13 | 57 | 2026-10-21T09:36:46.2338754Z |
| 2026-09-12 – 2026-09-12 | 65 | 2026-10-21T09:36:46.2338754Z |
| 2026-09-11 – 2026-09-11 | 63 | 2026-10-21T09:36:46.2338754Z |
| 2026-09-10 – 2026-09-10 | 187 | 2026-10-21T09:36:46.2338754Z |
| 2026-09-09 – 2026-09-09 | 138 | 2026-10-21T09:36:46.2338754Z |
| 2026-09-08 – 2026-09-08 | 0 | 2026-10-21T09:36:46.2338754Z |
| 2026-09-07 – 2026-09-07 | 0 | 2026-10-21T09:36:46.2338754Z |
| 2026-09-06 – 2026-09-06 | 231 | 2026-10-21T09:36:46.2338754Z |
| 2026-09-05 – 2026-09-05 | 142 | 2026-10-21T09:36:46.2338754Z |
| 2026-09-04 – 2026-09-04 | 174 | 2026-10-21T09:36:46.2338754Z |
| 2026-09-03 – 2026-09-03 | 0 | 2026-10-21T09:36:46.2338754Z |
| 2026-09-02 – 2026-09-02 | 135 | 2026-10-21T09:36:46.2338754Z |
| 2026-09-01 – 2026-09-01 | 144 | 2026-10-21T09:36:46.2338754Z |
| 2026-08-31 – 2026-08-31 | 201 | 2026-10-21T09:36:46.2338754Z |
| 2026-08-30 – 2026-08-30 | 32 | 2026-10-21T09:36:46.2338754Z |
| 2026-08-29 – 2026-08-29 | 78 | 2026-10-21T09:36:46.2338754Z |
| 2026-08-28 – 2026-08-28 | 142 | 2026-10-21T09:36:46.2338754Z |
| 2026-08-27 – 2026-08-27 | 147 | 2026-10-21T09:36:46.2338754Z |
| 2026-08-26 – 2026-08-26 | 137 | 2026-10-21T09:36:46.2338754Z |
| 2026-08-25 – 2026-08-25 | 66 | 2026-10-21T09:36:46.2338754Z |
| 2026-08-24 – 2026-08-24 | 178 | 2026-10-21T09:36:46.2338754Z |
| 2026-08-23 – 2026-08-23 | 169 | 2026-10-21T09:36:46.2338754Z |
| 2026-08-22 – 2026-08-22 | 93 | 2026-10-21T09:36:46.2338754Z |
| 2026-08-21 – 2026-08-21 | 116 | 2026-10-21T09:36:46.2338754Z |
| 2026-08-20 – 2026-08-20 | 239 | 2026-10-21T09:36:46.2338754Z |
| 2026-08-19 – 2026-08-19 | 193 | 2026-10-21T09:36:46.2338754Z |
| 2026-08-18 – 2026-08-18 | 134 | 2026-10-21T09:36:46.2338754Z |
| 2026-08-17 – 2026-08-17 | 125 | 2026-10-21T09:36:46.2338754Z |
| 2026-08-16 – 2026-08-16 | 107 | 2026-10-21T09:36:46.2338754Z |
| 2026-08-15 – 2026-08-15 | 90 | 2026-10-21T09:36:46.2338754Z |
| 2026-08-14 – 2026-08-14 | 0 | 2026-10-21T09:36:46.2338754Z |
| 2026-08-13 – 2026-08-13 | 130 | 2026-10-21T09:36:46.2338754Z |
| 2026-08-12 – 2026-08-12 | 175 | 2026-10-21T09:36:46.2338754Z |
| 2026-08-11 – 2026-08-11 | 137 | 2026-10-21T09:36:46.2338754Z |
| 2026-08-10 – 2026-08-10 | 58 | 2026-10-21T09:36:46.2338754Z |
| 2026-08-09 – 2026-08-09 | 26 | 2026-10-21T09:36:46.2338754Z |
| 2026-08-08 – 2026-08-08 | 121 | 2026-10-21T09:36:46.2338754Z |
| 2026-08-07 – 2026-08-07 | 130 | 2026-10-21T09:36:46.2338754Z |
| 2026-08-06 – 2026-08-06 | 111 | 2026-10-21T09:36:46.2338754Z |
| 2026-08-05 – 2026-08-05 | 109 | 2026-10-21T09:36:46.2338754Z |
| 2026-08-04 – 2026-08-04 | 83 | 2026-10-21T09:36:46.2338754Z |
| 2026-08-03 – 2026-08-03 | 16 | 2026-10-21T09:36:46.2338754Z |
| 2026-08-02 – 2026-08-02 | 45 | 2026-10-21T09:36:46.2338754Z |
| 2026-08-01 – 2026-08-01 | 16 | 2026-10-21T09:36:46.2338754Z |
| 2026-07-31 – 2026-07-31 | 74 | 2026-10-21T09:36:46.2338754Z |
| 2026-07-30 – 2026-07-30 | 88 | 2026-10-21T09:36:46.2338754Z |
| 2026-07-29 – 2026-07-29 | 97 | 2026-10-21T09:36:46.2338754Z |
| 2026-07-28 – 2026-07-28 | 35 | 2026-10-21T09:36:46.2338754Z |
| 2026-07-27 – 2026-07-27 | 115 | 2026-10-21T09:36:46.2338754Z |
| 2026-07-26 – 2026-07-26 | 38 | 2026-10-21T09:36:46.2338754Z |
| 2026-07-25 – 2026-07-25 | 36 | 2026-10-21T09:36:46.2338754Z |
| 2026-07-24 – 2026-07-24 | 90 | 2026-10-21T09:36:46.2338754Z |
| 2026-07-23 – 2026-07-23 | 31 | 2026-10-21T09:36:46.2338754Z |
This describes our collected data; it is not a security assessment.
Observed since 20 Oct, 19:12 UTC
No superseded metadata states collected. Unchanged observations do not create new revisions.
package:bcd487a401c5129e21f1bf64c98d736758d794c4acce51edf562013ed0541da7