Password breaches cost companies millions and destroy user trust. As developers, we have a responsibility to handle passwords securely. One mistake can expose thousands of users to identity theft and account takeover.
Passwords must be hashed using modern algorithms before storage. If your database gets breached, hashed passwords are useless to attackers without the ability to reverse them.
Not all hashing is created equal:
const bcrypt = require('bcrypt');
const saltRounds = 12;
// Hashing a password
async function hashPassword(plainPassword) {
const hash = await bcrypt.hash(plainPassword, saltRounds);
return hash;
}
// Verifying a password
async function verifyPassword(plainPassword, hash) {
const match = await bcrypt.compare(plainPassword, hash);
return match; // true or false
}
from argon2 import PasswordHasher
ph = PasswordHasher()
# Hash password
hash = ph.hash("my_password")
# Verify password
try:
ph.verify(hash, "my_password")
print("Password correct")
except:
print("Password incorrect")
Strong passwords are the first line of defense. They should be:
Set sensible requirements:
// Good password policy
- Minimum 12 characters
- At least one uppercase letter
- At least one lowercase letter
- At least one number
- At least one special character
- No common passwords (check against breach databases)
A salt is random data added to passwords before hashing. This prevents:
Example without salt:
// Two users, same password
User1: "password123" → hash: 5f4dcc3b5aa765d61d8327deb882cf99
User2: "password123" → hash: 5f4dcc3b5aa765d61d8327deb882cf99
// ⚠️ Attacker sees they're identical!
Example with salt:
// Two users, same password, different salts
User1: "password123" + salt1 → hash: a7f5c9d2e...
User2: "password123" + salt2 → hash: b3e8d1a4f...
// ✅ Hashes look completely different
Your database should store:
users table:
- id
- username
- email
- password_hash (NOT plain text!)
- password_salt (if not using bcrypt/argon2)
- password_updated_at
- failed_login_attempts
- account_locked_until
Limit login attempts to prevent automated attacks:
Failed attempts | Delay before next attempt
1 | 0 seconds
2 | 2 seconds
3 | 4 seconds
4 | 8 seconds
5 | 16 seconds (+ CAPTCHA)
Passwords alone aren't enough. Implement MFA using:
Password resets are a common attack vector. Secure them properly:
// Secure password reset flow
1. User requests reset
2. Generate random token (32+ bytes)
3. Store token hash + expiry in database
4. Send email with reset link
5. User clicks link, validates token
6. Allow password change
7. Invalidate token
8. Log out all sessions
9. Send confirmation email
Email is not secure. Never send passwords (or password reset links without expiry) via email.
They're just passwords with publicly guessable answers. Don't use them. If required, treat them as password-equivalent secrets.
Always validate and hash on the server. Client-side validation can be bypassed easily.
These make passwords easier to guess. Don't implement them.
Research shows this reduces security by encouraging predictable patterns (Password1, Password2, etc.). Only require changes after suspected compromise.
Check passwords against breach databases:
Use our Password Generator to create cryptographically secure passwords with customizable rules. Also check out our Hash Generator to understand different hashing algorithms.
Password security isn't optional - it's a fundamental responsibility. Use modern hashing algorithms, implement MFA, prevent brute force attacks, and never cut corners. Your users trust you with their security. Don't let them down.
← Back to Blog