Step-by-Step Guide
5 steps

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

1

Choose the right hashing algorithm

Use bcrypt (widely supported) or Argon2 (most modern and recommended).

Code Example
# Install bcrypt
npm install bcrypt

# Or Argon2 (preferred if your platform supports it)
npm install argon2
2

Hash passwords with proper salt rounds

Use at least 10 salt rounds for bcrypt (12 recommended).

Code Example
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)
}
3

Enforce password requirements

Set minimum length and complexity requirements.

Code Example
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.

4

Implement secure password storage in your signup flow

Hash the password before storing it in the database.

Code Example
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
  })
}
5

Implement secure login verification

Compare the provided password against the stored hash.

Code Example
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