A lightweight, zero-dependency, cryptographically secure OTP engine for Node.js applications with stateless HMAC verification and built-in Dev/Mock mode.
Most OTP libraries are either too basic or too bloated.
- π Zero Runtime Dependencies
- π¦ Single-Call API
- π Stateless & Tamper-Proof
- π‘οΈ Timing-Attack Proof
- π§ͺ Dev / Mock Mode
- π οΈ ESM + CJS Support
npm install @codeverselab/otp
# or
yarn add @codeverselab/otp
# or
pnpm add @codeverselab/otpimport { OtpManager } from '@codeverselab/otp';
// Initialize manager
const otpManager = new OtpManager({
otpSize: 6,
expiryInMinutes: 5,
secret: process.env.OTP_SECRET,
});
// Generate everything in a single call β‘
const { otp, hash, id, expiredAt, expiresInSeconds } = otpManager.generate();
console.log(otp); // "799159" -> Send via Email / SMS
console.log(hash); // "e555e4..." -> Return to client or save in session/DB
console.log(id); // "4f3bdb4e-..." -> Tracking ID
console.log(expiredAt); // 2026-07-24T15:20:37.641Z
console.log(expiresInSeconds); // 300
// Verify the user's input
const result = otpManager.verify({
otp: userInputOtp,
hash: clientHash,
id: clientId,
expiredAt: clientExpiredAt,
});
if (result.valid) {
console.log('OTP verified successfully!');
} else {
console.log('Verification failed:', result.reason); // 'EXPIRED' | 'INVALID_OTP'
}| Option | Type | Default | Description |
|---|---|---|---|
otpSize |
number |
6 |
Length of the generated numeric OTP. |
expiryInMinutes |
number |
5 |
Minutes until the generated OTP expires. |
secret |
string |
'codeverse-default-secret' |
HMAC secret key used for hash generation. |
isDev |
boolean |
false |
When true, forces OTP output to the mock value (000000). |
mockOtp |
string |
undefined |
Custom mock OTP value used when isDev is enabled. |
Testing OTP workflows locally can burn SMS credits and slow down developer velocity. @codeverselab/otp includes an optional Dev Mode that automatically substitutes generated OTPs with predictable values (like "000000") while keeping the underlying HMAC verification logic 100% real.
Set OTP_DEV_MODE=true in your .env file (when NODE_ENV !== 'production'):
NODE_ENV=development
OTP_DEV_MODE=trueconst otpManager = new OtpManager();
const { otp, isMock } = otpManager.generate();
console.log(otp); // "000000"
console.log(isMock); // trueconst devOtpManager = new OtpManager({
otpSize: 4,
isDev: true,
mockOtp: '1234', // Optional custom override
});
const { otp } = devOtpManager.generate();
console.log(otp); // "1234"import express from 'express';
import { OtpManager } from '@codeverselab/otp';
const app = express();
app.use(express.json());
const otpManager = new OtpManager({
otpSize: 6,
expiryInMinutes: 5,
secret: process.env.OTP_SECRET || 'fallback-secret',
});
// Route: Send OTP
app.post('/api/send-otp', async (req, res) => {
const { mobile } = req.body;
const { otp, hash, id, expiredAt, expiresInSeconds } = otpManager.generate();
// Send SMS using your preferred provider
await sendSmsProvider(mobile, `Your verification code is ${otp}`);
// Send metadata back to client for stateless verification
res.json({
success: true,
message: 'OTP sent successfully',
data: { hash, id, expiredAt, expiresInSeconds },
});
});
// Route: Verify OTP
app.post('/api/verify-otp', async (req, res) => {
const { otp, hash, id, expiredAt } = req.body;
const { valid, reason } = otpManager.verify({ otp, hash, id, expiredAt });
if (!valid) {
return res.status(400).json({
success: false,
error: reason === 'EXPIRED' ? 'OTP has expired' : 'Invalid OTP code',
});
}
res.json({ success: true, message: 'Phone number verified!' });
});generate(overrideSecret?: string): GeneratedOtp
Generates an OTP and returns a complete, ready-to-use payload:
interface GeneratedOtp {
otp: string; // Numeric OTP string (e.g. "849201")
hash: string; // SHA-256 HMAC hash
id: string; // UUID v4 tracking ID
expiredAt: Date; // Expiration timestamp
expiresInSeconds: number; // Duration in seconds
isMock?: boolean; // Indicates if generated under Dev mode
}verify(params): { valid: boolean; reason?: 'EXPIRED' | 'INVALID_OTP' }
Verifies an input OTP against the provided hash and metadata in constant time:
interface VerifyParams {
otp: string; // OTP code entered by the user
hash: string; // Stored or returned HMAC hash
id: string; // Unique generation ID
expiredAt: Date | string | number; // Expiration timestamp
secret?: string; // Optional override secret key
}@codeverselab/otp protects against common authentication attacks out of the box:
- Stateless HMAC Signatures: The generated hash is constructed using
HMAC-SHA256(secret, "${otp}.${id}.${expiredAt}"). An attacker cannot tamper with the expiration timestamp or swap the tracking ID without invalidating the signature. - Cryptographic Randomness: Numeric OTP generation uses Node's native
crypto.randomInt, which draws directly from system entropy (unlike insecureMath.random()). - Timing-Attack Resistance: Verification relies on
crypto.timingSafeEqualto compare hashes in constant time, neutralizing side-channel analysis.
Distributed under the MIT License.
Developed with β€οΈ by Codeverse Lab.