Overview
The validateOTP() function validates one-time passwords (OTP) with built-in attempt tracking and security features. It compares user input against the correct OTP code and enforces maximum attempt limits to prevent brute force attacks.
Common Use Cases
- Two-factor authentication (2FA)
- Email verification codes
- SMS verification codes
- Password reset flows
- Account recovery
- Login verification
Function Signature
validateOTP(otp, correctOTP, options)
Parameters
The OTP code entered by the user.
The correct OTP code to validate against.
Configuration options for OTP validation.The current number of failed attempts. Used to track and limit validation attempts.
Maximum number of attempts allowed before blocking further validation.
Whether to return detailed validation information instead of a boolean.
Return Value
When details is false: Returns true if OTP is valid, false otherwise.When details is true: Returns an object with the following properties:Whether the OTP is valid.
Array of validation error messages, or null if valid.
The OTP code that was provided.
The correct OTP code (for debugging - be careful with logging).
Current number of attempts.
Number of attempts remaining before lockout.
Maximum attempts allowed.
Examples
Basic OTP Validation
Simple Validation
With Attempt Tracking
Detailed Response
import { validateOTP } from 'validauth';
// Correct OTP
const isValid = validateOTP('123456', '123456');
// Returns: true
// Incorrect OTP
const isInvalid = validateOTP('123456', '654321');
// Returns: false
import { validateOTP } from 'validauth';
// First attempt
validateOTP('111111', '123456', {
attempts: 1,
maxAttempts: 3
});
// Returns: false
// Second attempt
validateOTP('222222', '123456', {
attempts: 2,
maxAttempts: 3
});
// Returns: false
// Third attempt - still allowed
validateOTP('123456', '123456', {
attempts: 3,
maxAttempts: 3
});
// Returns: true
// Fourth attempt - exceeded max
validateOTP('123456', '123456', {
attempts: 4,
maxAttempts: 3
});
// Returns: false (max attempts exceeded)
import { validateOTP } from 'validauth';
const result = validateOTP('123456', '123456', {
attempts: 2,
maxAttempts: 5,
details: true
});
console.log(result);
/* Returns:
{
valid: true,
errors: null,
otp: '123456',
correctOTP: '123456',
attempts: 2,
remainingAttempts: 3,
maxAttempts: 5
}
*/
Two-Factor Authentication Example
import { validateOTP } from 'validauth';
class TwoFactorAuth {
constructor() {
this.sessions = new Map();
}
// Generate and send OTP (simplified)
async sendOTP(userId, method = 'email') {
// Generate 6-digit OTP
const otp = Math.floor(100000 + Math.random() * 900000).toString();
// Store OTP with session data
this.sessions.set(userId, {
otp: otp,
attempts: 0,
expiresAt: Date.now() + 10 * 60 * 1000, // 10 minutes
method: method
});
// Send OTP via email/SMS
// await sendEmail(userId, otp);
return {
success: true,
message: `OTP sent via ${method}`
};
}
// Validate OTP with attempt tracking
verifyOTP(userId, userOTP) {
const session = this.sessions.get(userId);
if (!session) {
return {
success: false,
message: 'No OTP session found. Please request a new code.'
};
}
// Check expiration
if (Date.now() > session.expiresAt) {
this.sessions.delete(userId);
return {
success: false,
message: 'OTP has expired. Please request a new code.'
};
}
// Validate OTP with attempt tracking
const result = validateOTP(userOTP, session.otp, {
attempts: session.attempts,
maxAttempts: 3,
details: true
});
// Increment attempts
session.attempts++;
if (!result.valid) {
if (result.errors.includes('Max attempts exceeded.')) {
this.sessions.delete(userId);
return {
success: false,
message: 'Maximum attempts exceeded. Please request a new code.',
remainingAttempts: 0
};
}
return {
success: false,
message: 'Invalid OTP code.',
remainingAttempts: result.remainingAttempts
};
}
// Success - clean up session
this.sessions.delete(userId);
return {
success: true,
message: 'OTP verified successfully'
};
}
}
// Usage
const twoFA = new TwoFactorAuth();
// Send OTP
await twoFA.sendOTP('user123', 'email');
// User enters OTP
const result = twoFA.verifyOTP('user123', '123456');
if (result.success) {
console.log('2FA successful');
} else {
console.error(result.message);
console.log('Remaining attempts:', result.remainingAttempts);
}
Email Verification Flow
import { validateOTP } from 'validauth';
class EmailVerification {
constructor(database) {
this.db = database;
}
// Generate and send verification code
async sendVerificationCode(email) {
// Generate 6-digit code
const code = Math.floor(100000 + Math.random() * 900000).toString();
// Store in database
await this.db.verificationCodes.create({
email: email,
code: code,
attempts: 0,
expiresAt: new Date(Date.now() + 30 * 60 * 1000) // 30 minutes
});
// Send email
await this.sendEmail(email, code);
return { success: true };
}
// Verify the code
async verifyCode(email, userCode) {
// Get verification record
const record = await this.db.verificationCodes.findOne({ email });
if (!record) {
return {
verified: false,
message: 'No verification code found for this email'
};
}
// Check expiration
if (new Date() > record.expiresAt) {
await this.db.verificationCodes.delete({ email });
return {
verified: false,
message: 'Verification code has expired'
};
}
// Validate OTP
const result = validateOTP(userCode, record.code, {
attempts: record.attempts,
maxAttempts: 5,
details: true
});
// Update attempts
await this.db.verificationCodes.update(
{ email },
{ attempts: record.attempts + 1 }
);
if (!result.valid) {
if (result.remainingAttempts === 0) {
await this.db.verificationCodes.delete({ email });
return {
verified: false,
message: 'Too many failed attempts. Please request a new code.'
};
}
return {
verified: false,
message: `Invalid code. ${result.remainingAttempts} attempts remaining.`
};
}
// Success - mark email as verified
await this.db.users.update(
{ email },
{ emailVerified: true }
);
await this.db.verificationCodes.delete({ email });
return {
verified: true,
message: 'Email verified successfully'
};
}
async sendEmail(email, code) {
// Email sending implementation
console.log(`Sending code ${code} to ${email}`);
}
}
// Usage
const verification = new EmailVerification(database);
// Send code
await verification.sendVerificationCode('[email protected]');
// Verify code
const result = await verification.verifyCode('[email protected]', '123456');
console.log(result.message);
Password Reset Flow
import { validateOTP } from 'validauth';
import { isPassword } from 'validauth';
class PasswordReset {
constructor() {
this.resetCodes = new Map();
}
// Step 1: Request password reset
async requestReset(email) {
// Generate 6-digit reset code
const code = Math.floor(100000 + Math.random() * 900000).toString();
this.resetCodes.set(email, {
code: code,
attempts: 0,
expiresAt: Date.now() + 15 * 60 * 1000, // 15 minutes
verified: false
});
// Send reset code via email
// await sendEmail(email, code);
return {
success: true,
message: 'Reset code sent to your email'
};
}
// Step 2: Verify reset code
verifyResetCode(email, userCode) {
const resetData = this.resetCodes.get(email);
if (!resetData) {
return {
verified: false,
message: 'No reset request found'
};
}
if (Date.now() > resetData.expiresAt) {
this.resetCodes.delete(email);
return {
verified: false,
message: 'Reset code has expired'
};
}
const result = validateOTP(userCode, resetData.code, {
attempts: resetData.attempts,
maxAttempts: 3,
details: true
});
resetData.attempts++;
if (!result.valid) {
if (result.remainingAttempts === 0) {
this.resetCodes.delete(email);
}
return {
verified: false,
message: 'Invalid reset code',
remainingAttempts: result.remainingAttempts
};
}
// Mark as verified
resetData.verified = true;
return {
verified: true,
message: 'Code verified. You can now reset your password.'
};
}
// Step 3: Reset password
resetPassword(email, newPassword) {
const resetData = this.resetCodes.get(email);
if (!resetData || !resetData.verified) {
return {
success: false,
message: 'Invalid or unverified reset request'
};
}
// Validate new password
const passwordValid = isPassword(newPassword, {
minLength: 8,
details: true
});
if (!passwordValid.valid) {
return {
success: false,
message: 'Password does not meet requirements',
errors: passwordValid.errors
};
}
// Update password in database
// await db.users.updatePassword(email, newPassword);
// Clean up
this.resetCodes.delete(email);
return {
success: true,
message: 'Password reset successfully'
};
}
}
// Usage
const resetService = new PasswordReset();
// Step 1: Request reset
await resetService.requestReset('[email protected]');
// Step 2: Verify code
const verification = resetService.verifyResetCode('[email protected]', '123456');
if (verification.verified) {
// Step 3: Reset password
const reset = resetService.resetPassword('[email protected]', 'NewSecureP@ss123');
console.log(reset.message);
}
Security Best Practices
Critical Security Considerations
- Never log or expose the
correctOTP value in production
- Always set an expiration time for OTP codes (5-30 minutes typical)
- Implement rate limiting at the API level to prevent brute force
- Use secure random number generation for OTP codes
- Clear OTP data after successful validation
- Consider using time-based OTP (TOTP) for enhanced security
Generating Secure OTP Codes
import crypto from 'crypto';
function generateSecureOTP(length = 6) {
// Use cryptographically secure random number generation
const buffer = crypto.randomBytes(length);
const otp = Array.from(buffer)
.map(byte => byte % 10)
.join('');
return otp.padStart(length, '0');
}
// Generate 6-digit OTP
const otp = generateSecureOTP(6);
console.log(otp); // e.g., "472916"
Rate Limiting Example
import { validateOTP } from 'validauth';
class RateLimitedOTP {
constructor() {
this.attempts = new Map();
}
isRateLimited(identifier, windowMs = 60000, maxRequests = 5) {
const now = Date.now();
const userAttempts = this.attempts.get(identifier) || [];
// Remove old attempts outside the time window
const recentAttempts = userAttempts.filter(
timestamp => now - timestamp < windowMs
);
if (recentAttempts.length >= maxRequests) {
return true;
}
// Record this attempt
recentAttempts.push(now);
this.attempts.set(identifier, recentAttempts);
return false;
}
validateWithRateLimit(identifier, otp, correctOTP, options = {}) {
// Check rate limit (5 attempts per minute)
if (this.isRateLimited(identifier)) {
return {
valid: false,
message: 'Too many attempts. Please try again later.',
rateLimited: true
};
}
// Validate OTP
const result = validateOTP(otp, correctOTP, {
...options,
details: true
});
return {
valid: result.valid,
message: result.valid ? 'OTP valid' : 'Invalid OTP',
remainingAttempts: result.remainingAttempts,
rateLimited: false
};
}
}
Validation Rules
OTP Validation Requirements
- OTP and correctOTP must match exactly (case-sensitive)
- Attempts are tracked independently (you manage the counter)
- Default maximum attempts: 3
- Validation fails if attempts exceed maxAttempts
- No automatic expiration (implement separately)
Error Messages
The validator returns the following error messages:
"Invalid OTP." - The OTP does not match the correct code
"Max attempts exceeded." - The number of attempts has exceeded the limit
"Max attempts must be greater than 0." - Invalid maxAttempts configuration
Best Practices
Recommended Implementation
- Set OTP expiration time (10-30 minutes)
- Use 6-digit codes for user convenience
- Limit to 3-5 validation attempts
- Implement rate limiting at API level
- Clear OTP data after successful validation
- Send OTP via secure channels (email/SMS)
- Allow users to request new codes
- Log failed attempts for security monitoring
Testing OTP Flows
import { validateOTP } from 'validauth';
// In development/testing, you might want to use fixed codes
const isDevelopment = process.env.NODE_ENV === 'development';
function getTestOTP() {
return isDevelopment ? '123456' : generateSecureOTP();
}
// For testing, always accept a specific test code
function validateOTPWithTestMode(userOTP, correctOTP, options = {}) {
if (isDevelopment && userOTP === '000000') {
return true; // Test code always works in dev
}
return validateOTP(userOTP, correctOTP, options);
}