How to Hash Passwords Securely
Never store passwords in plaintext. Proper password hashing uses a slow, salted algorithm that makes brute-force attacks impractical. This guide covers choosing the right algorithm, configuring work factors, and implementing password hashing correctly.
Find security issues automatically before attackers do.
Follow These Steps
Choose the right hashing algorithm
Use bcrypt (widely supported) or Argon2 (most modern and recommended).
# Install bcrypt
npm install bcrypt
# Or Argon2 (preferred if your platform supports it)
npm install argon2Hash passwords with proper salt rounds
Use at least 10 salt rounds for bcrypt (12 recommended).
import bcrypt from 'bcrypt'
const SALT_ROUNDS = 12 // Recommended minimum
async function hashPassword(password: string): Promise<string> {
return bcrypt.hash(password, SALT_ROUNDS)
}
async function verifyPassword(password: string, hash: string): Promise<boolean> {
return bcrypt.compare(password, hash)
}
// With Argon2
import argon2 from 'argon2'
async function hashPassword(password: string): Promise<string> {
return argon2.hash(password, {
type: argon2.argon2id,
memoryCost: 65536, // 64 MB
timeCost: 3,
parallelism: 4
})
}
async function verifyPassword(password: string, hash: string): Promise<boolean> {
return argon2.verify(hash, password)
}Enforce password requirements
Set minimum length and complexity requirements.
import { z } from 'zod'
const PasswordSchema = z.string()
.min(8, 'Password must be at least 8 characters')
.max(128, 'Password must be less than 128 characters')
.regex(/[a-z]/, 'Must contain a lowercase letter')
.regex(/[A-Z]/, 'Must contain an uppercase letter')
.regex(/[0-9]/, 'Must contain a number')Longer passwords are more secure than complex short ones. Consider requiring 12+ characters without complexity rules as an alternative.
Implement secure password storage in your signup flow
Hash the password before storing it in the database.
async function signup(email: string, password: string) {
// Validate password strength
PasswordSchema.parse(password)
// Hash before storing
const hashedPassword = await hashPassword(password)
// Store hash (never the plaintext password)
await db.insert(users).values({
email,
password: hashedPassword
})
}Implement secure login verification
Compare the provided password against the stored hash.
async function login(email: string, password: string) {
const user = await db.query.users.findFirst({
where: eq(users.email, email)
})
// Use constant-time comparison (bcrypt.compare does this)
if (!user || !(await verifyPassword(password, user.password))) {
// Same error for wrong email and wrong password (prevent enumeration)
throw new Error('Invalid email or password')
}
return createSession(user)
}What You'll Achieve
Passwords are hashed with bcrypt or Argon2 using proper work factors, validated for strength before storage, and verified securely during login. No plaintext passwords are stored anywhere.
Common Mistakes to Avoid
Mistake
Using MD5 or SHA-256 for password hashing
Fix
MD5 and SHA are fast hashes designed for data integrity, not passwords. Use bcrypt or Argon2 which are intentionally slow to resist brute force.
Mistake
Using low salt rounds (< 10)
Fix
Low salt rounds make hashing fast, which helps attackers. Use at least 10 rounds for bcrypt, 12 is recommended.
Mistake
Returning different errors for wrong email vs wrong password
Fix
This enables user enumeration. Always return the same error message regardless of whether the email exists or the password is wrong.
Frequently Asked Questions
Is bcrypt still secure in 2026?
Yes. bcrypt with 12+ salt rounds remains secure. Argon2id is the newer recommendation but bcrypt is still widely used and considered safe.
Should I pepper passwords?
Peppering (adding a server-side secret to the password before hashing) adds defense-in-depth. It helps if the database is compromised but the application server is not. It is optional but recommended.
Ready to Secure Your App?
VAS automatically scans your deployed app for the security issues covered in this guide. Get actionable results in minutes.
Start Security Scan