otpkit.js
v1.0.0
Published
Lightweight, secure OTP generation and verification for Node.js applications. Framework-agnostic, production-ready, and easy to integrate.
Downloads
13
Maintainers
Readme
🔐 otpkit.js
Lightweight, secure OTP generation and verification for Node.js applications.
A simple, framework-agnostic library for generating and verifying one-time passwords (OTPs) with bcrypt hashing and built-in expiry management. Perfect for email/SMS verification, password resets, and two-factor authentication flows.
✨ Features
- 🔒 Secure by Default — OTPs are hashed using bcrypt before storage
- ⏱️ Built-in Expiry — Automatic TTL (time-to-live) handling
- 🎯 Zero Configuration — Works out of the box with sensible defaults
- 📦 Lightweight — Minimal dependencies, small footprint
- 🔧 Framework Agnostic — Works with Express, Fastify, Koa, Hono, or vanilla Node.js
- 🎲 Cryptographically Random — Uses Node.js
crypto.randomInt()for secure OTP generation - 📝 TypeScript Friendly — Clean API with predictable return types
📦 Installation
npm install otpkit.jsyarn add otpkit.jspnpm add otpkit.js🚀 Quick Start
import { createOTP, verifyOTP } from "otpkit.js";
// Generate an OTP
const { otp, hash, expiresAt } = await createOTP();
console.log(otp); // "482916" → Send this to the user (email/SMS)
console.log(hash); // "$2a$10$..." → Store this in your database
console.log(expiresAt); // 1703001234567 → Unix timestamp when OTP expires
// Later, verify the OTP entered by the user
const result = await verifyOTP({
otp: "482916", // User's input
hash: hash, // Retrieved from database
expiresAt: expiresAt
});
if (result.valid) {
console.log("OTP verified successfully!");
} else {
console.log("Verification failed:", result.reason);
// result.reason: "OTP_EXPIRED" | "OTP_INVALID"
}📖 API Reference
createOTP(options?)
Generates a new OTP with a bcrypt hash.
Parameters
| Parameter | Type | Default | Description |
|-----------|----------|---------|--------------------------------------|
| length | number | 6 | Length of the OTP (minimum: 4) |
| ttl | number | 300 | Time-to-live in seconds (5 minutes) |
Returns
{
otp: string, // The plaintext OTP to send to the user
hash: string, // Bcrypt hash to store in your database
expiresAt: number, // Unix timestamp (ms) when the OTP expires
ttl: number // The TTL value used
}Examples
// Default: 6-digit OTP, 5-minute expiry
const { otp, hash, expiresAt } = await createOTP();
// Custom: 8-digit OTP, 10-minute expiry
const { otp, hash, expiresAt } = await createOTP({ length: 8, ttl: 600 });
// Short-lived: 4-digit OTP, 2-minute expiry
const { otp, hash, expiresAt } = await createOTP({ length: 4, ttl: 120 });verifyOTP(options)
Verifies an OTP against its hash, checking both validity and expiration.
Parameters
| Parameter | Type | Required | Description |
|-------------|----------|----------|----------------------------------------|
| otp | string | Yes | The OTP entered by the user |
| hash | string | Yes | The bcrypt hash stored in your database|
| expiresAt | number | Yes | The expiration timestamp |
Returns
{
valid: boolean, // Whether the OTP is valid
reason: string | null // null if valid, otherwise "OTP_EXPIRED" or "OTP_INVALID"
}Examples
// Successful verification
const result = await verifyOTP({ otp: "482916", hash, expiresAt });
// { valid: true, reason: null }
// Expired OTP
const result = await verifyOTP({ otp: "482916", hash, expiresAt: Date.now() - 1000 });
// { valid: false, reason: "OTP_EXPIRED" }
// Invalid OTP
const result = await verifyOTP({ otp: "000000", hash, expiresAt });
// { valid: false, reason: "OTP_INVALID" }💡 Usage Examples
Email Verification Flow
import { createOTP, verifyOTP } from "otpkit.js";
import { sendEmail } from "./your-email-service.js";
import { db } from "./your-database.js";
// Step 1: User requests email verification
async function requestEmailVerification(userId, email) {
const { otp, hash, expiresAt } = await createOTP({ ttl: 600 }); // 10 min expiry
// Store hash and expiry in database
await db.users.update(userId, {
emailVerificationHash: hash,
emailVerificationExpiry: expiresAt
});
// Send OTP to user's email
await sendEmail({
to: email,
subject: "Your Verification Code",
body: `Your verification code is: ${otp}`
});
}
// Step 2: User submits the OTP
async function verifyEmail(userId, userOtp) {
const user = await db.users.findById(userId);
const result = await verifyOTP({
otp: userOtp,
hash: user.emailVerificationHash,
expiresAt: user.emailVerificationExpiry
});
if (result.valid) {
await db.users.update(userId, {
emailVerified: true,
emailVerificationHash: null,
emailVerificationExpiry: null
});
return { success: true };
}
return { success: false, error: result.reason };
}Express.js Integration
import express from "express";
import { createOTP, verifyOTP } from "otpkit.js";
const app = express();
app.use(express.json());
// In-memory store (use Redis/DB in production)
const otpStore = new Map();
app.post("/api/otp/send", async (req, res) => {
const { phone } = req.body;
const { otp, hash, expiresAt } = await createOTP();
otpStore.set(phone, { hash, expiresAt });
// Send OTP via SMS service
console.log(`Send OTP ${otp} to ${phone}`);
res.json({ message: "OTP sent successfully" });
});
app.post("/api/otp/verify", async (req, res) => {
const { phone, otp } = req.body;
const stored = otpStore.get(phone);
if (!stored) {
return res.status(400).json({ error: "No OTP found for this number" });
}
const result = await verifyOTP({
otp,
hash: stored.hash,
expiresAt: stored.expiresAt
});
if (result.valid) {
otpStore.delete(phone);
return res.json({ success: true });
}
res.status(400).json({ error: result.reason });
});
app.listen(3000);🔒 Security Considerations
- Never log or expose the plaintext OTP — Only send it to the user via a secure channel
- Store only the hash — Never store the plaintext OTP in your database
- Implement rate limiting — Prevent brute-force attacks on OTP verification
- Use HTTPS — Always transmit OTPs over encrypted connections
- Consider attempt limits — Lock out users after multiple failed verification attempts
- Clean up expired OTPs — Regularly purge expired OTP records from your database
🤝 Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add some amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
📄 License
This project is licensed under the MIT License - see the LICENSE file for details.
👤 Author
Ashutosh Swamy
- GitHub: @ashutoshswamy
