HMAC-based (HOTP) and Time-based (TOTP) One-Time Password library compatible with Google Authenticator
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
HOTP implements RFC 4226 counter-based one-time passwords. Unlike time-based systems, HOTP uses an incrementing counter that must be synchronized between client and server.
Generates tokens based on a secret key and counter value.
/**
* Generate an HOTP token using secret and counter
* @param secret - Secret key in the configured encoding
* @param counter - Counter value (must be synchronized)
* @returns Token string with configured digit length
*/
generate(secret: string, counter: number): string;Usage Example:
import { hotp } from "otplib";
const secret = "your-secret-key";
let counter = 0;
// Generate tokens with incrementing counter
const token1 = hotp.generate(secret, counter++);
const token2 = hotp.generate(secret, counter++);
const token3 = hotp.generate(secret, counter++);
console.log(token1); // "123456"
console.log(token2); // "789012"
console.log(token3); // "345678"
// Configure custom options
hotp.options = { digits: 8, algorithm: 'sha256' };
const longToken = hotp.generate(secret, counter++);
console.log(longToken); // "12345678"Verifies tokens against a specific counter value. The counter must match exactly.
/**
* Verify an HOTP token against secret and counter
* @param token - Token to verify
* @param secret - Secret key
* @param counter - Expected counter value
* @returns true if token matches the generated token for this counter
*/
check(token: string, secret: string, counter: number): boolean;
/**
* Object-based token verification
* @param opts - Object containing token, secret, and counter
* @returns true if token is valid for the given counter
*/
verify(opts: { token: string; secret: string; counter: number }): boolean;Usage Examples:
import { hotp } from "otplib";
const secret = "your-secret-key";
const counter = 5;
const token = "123456";
// Method 1: Direct parameters
const isValid = hotp.check(token, secret, counter);
// Method 2: Object parameters
const isValid2 = hotp.verify({ token, secret, counter });
console.log(isValid); // true or false
// Counter synchronization example
let serverCounter = 0;
let maxLookAhead = 10;
function verifyHotpWithSync(token: string, secret: string): boolean {
// Try current counter and look ahead
for (let i = 0; i <= maxLookAhead; i++) {
if (hotp.check(token, secret, serverCounter + i)) {
serverCounter = serverCounter + i + 1; // Update for next token
return true;
}
}
return false;
}Generate otpauth:// URIs for HOTP setup with counter information.
/**
* Generate an otpauth URI for HOTP setup
* @param accountName - User identifier
* @param issuer - Service name
* @param secret - Secret key
* @param counter - Initial counter value
* @returns otpauth://hotp/ URI string
*/
keyuri(accountName: string, issuer: string, secret: string, counter: number): string;Usage Example:
import { hotp } from "otplib";
const secret = "your-secret-key";
const user = "user@example.com";
const service = "My Service";
const initialCounter = 0;
const otpauth = hotp.keyuri(user, service, secret, initialCounter);
console.log(otpauth);
// "otpauth://hotp/My%20Service:user@example.com?secret=your-secret-key&counter=0&issuer=My%20Service"Manage HOTP-specific configuration options.
/**
* Get/set configuration options
*/
options: Partial<HOTPOptions>;
/**
* Reset options to default values
*/
resetOptions(): void;
/**
* Get all options with defaults applied
* @returns Complete options object
*/
allOptions(): Readonly<HOTPOptions>;
/**
* Create new instance with custom defaults
* @param defaultOptions - Custom default options
* @returns New HOTP instance
*/
create(defaultOptions?: Partial<HOTPOptions>): HOTP;Usage Examples:
import { hotp } from "otplib";
// Configure options
hotp.options = {
digits: 8, // 8-digit tokens
algorithm: 'sha256', // SHA-256 HMAC
encoding: 'hex' // Hex-encoded secrets
};
// Create instance with custom defaults
const customHotp = hotp.create({
digits: 6,
algorithm: 'sha1',
encoding: 'ascii'
});
// Reset to library defaults
hotp.resetOptions();
// View complete configuration
const config = hotp.allOptions();
console.log(config.digits); // 6
console.log(config.algorithm); // 'sha1'import { hotp } from "otplib";
class HotpService {
private counters = new Map<string, number>();
generateToken(userId: string, secret: string): string {
const counter = this.getCounter(userId);
const token = hotp.generate(secret, counter);
this.incrementCounter(userId);
return token;
}
verifyToken(userId: string, secret: string, token: string): boolean {
const counter = this.getCounter(userId);
return hotp.check(token, secret, counter);
}
private getCounter(userId: string): number {
return this.counters.get(userId) || 0;
}
private incrementCounter(userId: string): void {
const current = this.getCounter(userId);
this.counters.set(userId, current + 1);
}
}import { hotp } from "otplib";
class HotpSyncService {
private counters = new Map<string, number>();
private readonly LOOK_AHEAD_WINDOW = 10;
verifyTokenWithSync(userId: string, secret: string, token: string): boolean {
const baseCounter = this.getCounter(userId);
// Try current counter and look ahead
for (let i = 0; i <= this.LOOK_AHEAD_WINDOW; i++) {
const testCounter = baseCounter + i;
if (hotp.check(token, secret, testCounter)) {
// Update counter to one past the successful counter
this.setCounter(userId, testCounter + 1);
return true;
}
}
return false;
}
private getCounter(userId: string): number {
return this.counters.get(userId) || 0;
}
private setCounter(userId: string, counter: number): void {
this.counters.set(userId, counter);
}
}import { hotp } from "otplib";
class HardwareTokenSimulator {
private counter: number = 0;
constructor(private secret: string) {}
// Simulate pressing the button on a hardware token
pressButton(): string {
const token = hotp.generate(this.secret, this.counter);
this.counter++;
return token;
}
// Reset counter (usually requires physical reset)
resetCounter(newCounter: number = 0): void {
this.counter = newCounter;
}
getCurrentCounter(): number {
return this.counter;
}
}
// Usage
const token = new HardwareTokenSimulator("shared-secret");
const firstToken = token.pressButton(); // Uses counter 0
const secondToken = token.pressButton(); // Uses counter 1interface HOTPOptions {
/** HMAC algorithm to use */
algorithm: 'sha1' | 'sha256' | 'sha512';
/** Number of digits in generated token */
digits: number;
/** Encoding format of the secret key */
encoding: 'ascii' | 'base64' | 'hex' | 'latin1' | 'utf8';
/** Function to create HMAC digest */
createDigest: (algorithm: string, key: string, data: string) => string;
/** Function to create HMAC key from secret */
createHmacKey: (algorithm: string, secret: string, encoding: string) => string;
/** Pre-computed digest (advanced usage) */
digest?: string;
}Install with Tessl CLI
npx tessl i tessl/npm-otplib