Learn how to properly handle passwords in your applications with modern security practices.
1. Password Hashing with bcrypt
// Install bcrypt
npm install bcrypt
// Password hashing implementation
const bcrypt = require('bcrypt');
const saltRounds = 12; // Higher number = more secure but slower
async function hashPassword(password) {
const salt = await bcrypt.genSalt(saltRounds);
return bcrypt.hash(password, salt);
}
async function verifyPassword(password, hash) {
return bcrypt.compare(password, hash);
}
// Usage example
async function registerUser(username, password) {
const hashedPassword = await hashPassword(password);
// Store username and hashedPassword in database
return { username, password: hashedPassword };
}
async function loginUser(username, password) {
const user = await getUserFromDB(username);
if (!user) return false;
const isValid = await verifyPassword(password, user.password);
return isValid ? user : false;
}
2. Password Policy Implementation
// Password validation rules
const passwordRules = {
minLength: 12,
requireUppercase: true,
requireLowercase: true,
requireNumbers: true,
requireSpecialChars: true,
maxConsecutiveChars: 3,
bannedPasswords: ['password123', 'qwerty', '123456']
};
function validatePassword(password) {
const errors = [];
if (password.length < passwordRules.minLength) {
errors.push(`Password must be at least ${passwordRules.minLength} characters long`);
}
if (passwordRules.requireUppercase && !/[A-Z]/.test(password)) {
errors.push('Password must contain at least one uppercase letter');
}
if (passwordRules.requireLowercase && !/[a-z]/.test(password)) {
errors.push('Password must contain at least one lowercase letter');
}
if (passwordRules.requireNumbers && !/[0-9]/.test(password)) {
errors.push('Password must contain at least one number');
}
if (passwordRules.requireSpecialChars && !/[!@#$%^&*]/.test(password)) {
errors.push('Password must contain at least one special character');
}
if (passwordRules.bannedPasswords.includes(password)) {
errors.push('This password is too common and not allowed');
}
// Check for consecutive characters
const consecutiveRegex = new RegExp(`(.)\\1{${passwordRules.maxConsecutiveChars},}`);
if (consecutiveRegex.test(password)) {
errors.push(`Password cannot contain more than ${passwordRules.maxConsecutiveChars} consecutive characters`);
}
return {
isValid: errors.length === 0,
errors
};
}
3. Rate Limiting and Account Lockout
// Using express-rate-limit
const rateLimit = require('express-rate-limit');
const Redis = require('ioredis');
const redis = new Redis();
// Login attempt tracking
async function trackLoginAttempt(username) {
const key = `login_attempts:${username}`;
const attempts = await redis.incr(key);
await redis.expire(key, 3600); // Reset after 1 hour
return attempts;
}
// Rate limiting middleware
const loginLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 5, // 5 attempts per window
handler: async (req, res) => {
const attempts = await trackLoginAttempt(req.body.username);
if (attempts >= 10) {
// Lock account for 24 hours
await redis.set(`account_locked:${req.body.username}`, '1', 'EX', 86400);
return res.status(429).json({
error: 'Too many login attempts. Account locked for 24 hours.'
});
}
res.status(429).json({
error: 'Too many login attempts. Please try again later.'
});
}
});
app.post('/login', loginLimiter, async (req, res) => {
const { username, password } = req.body;
// Check if account is locked
const isLocked = await redis.get(`account_locked:${username}`);
if (isLocked) {
return res.status(403).json({
error: 'Account is temporarily locked. Please try again later.'
});
}
// Proceed with login...
});
4. Password Reset Flow
// Password reset implementation
const crypto = require('crypto');
async function generateResetToken() {
return crypto.randomBytes(32).toString('hex');
}
async function sendPasswordResetEmail(email) {
const token = await generateResetToken();
const expires = Date.now() + 3600000; // 1 hour
// Store token in database
await storeResetToken(email, token, expires);
// Send email with reset link
const resetLink = `https://yourdomain.com/reset-password?token=${token}`;
await sendEmail(email, {
subject: 'Password Reset Request',
text: `Click here to reset your password: ${resetLink}\nThis link will expire in 1 hour.`
});
}
async function resetPassword(token, newPassword) {
// Validate token and expiration
const resetData = await getResetToken(token);
if (!resetData || resetData.expires < Date.now()) {
throw new Error('Invalid or expired reset token');
}
// Validate new password
const validation = validatePassword(newPassword);
if (!validation.isValid) {
throw new Error(validation.errors.join(', '));
}
// Update password
const hashedPassword = await hashPassword(newPassword);
await updateUserPassword(resetData.email, hashedPassword);
// Invalidate token
await invalidateResetToken(token);
}
⚠️ Common Security Mistakes
- Storing passwords in plain text
- Using weak hashing algorithms (MD5, SHA1)
- Not implementing rate limiting
- Allowing unlimited password attempts
- Not enforcing strong password policies
✅ Best Practices
- Use bcrypt or Argon2 for password hashing
- Implement strong password policies
- Add rate limiting for login attempts
- Use secure password reset flows
- Regularly audit password security
Password Requirements Checklist
- Minimum length: 12 characters
- Must include uppercase and lowercase letters
- Must include numbers and special characters
- No common passwords or patterns
- No more than 3 consecutive identical characters
- Not similar to username or email