Skip to main content

hashPassword()

Hashes a plain text password using bcrypt with a salt round of 10.
password
string
required
The plain text password to hash
return
Promise<string>
A promise that resolves to the hashed password string

Function Signature

async function hashPassword(password: string): Promise<string>

Implementation Details

  • Uses bcryptjs library for secure password hashing
  • Salt rounds are set to 10 (balances security and performance)
  • Generates a unique salt for each password
  • The hash includes the salt, so no separate salt storage is needed

Security Considerations

  • Never store plain text passwords
  • The salt rounds value (10) provides adequate security while maintaining reasonable performance
  • Each password gets a unique salt automatically
  • Bcrypt is designed to be slow to prevent brute force attacks

Example

import { hashPassword } from '@/lib/auth/session';
import { createUser } from '@/lib/db/queries';

export async function registerUser(email: string, password: string, name: string) {
  // Hash the password before storing
  const passwordHash = await hashPassword(password);
  
  // Create user with hashed password
  const user = await createUser({
    email,
    passwordHash,
    name,
    role: 'member'
  });
  
  return user;
}

comparePasswords()

Compares a plain text password with a hashed password to verify if they match.
plainTextPassword
string
required
The plain text password to verify (e.g., from user input)
hashedPassword
string
required
The hashed password to compare against (e.g., from the database)
return
Promise<boolean>
A promise that resolves to true if the passwords match, false otherwise

Function Signature

async function comparePasswords(
  plainTextPassword: string,
  hashedPassword: string
): Promise<boolean>

Implementation Details

  • Uses bcryptjs library’s secure comparison function
  • Automatically extracts the salt from the hashed password
  • Timing-safe comparison to prevent timing attacks
  • Returns a boolean indicating whether the passwords match

Security Considerations

  • Always use this function instead of direct string comparison
  • The comparison is constant-time to prevent timing attacks
  • Never log or expose password comparison results in detail
  • Implement rate limiting on authentication endpoints to prevent brute force

Example

import { comparePasswords } from '@/lib/auth/session';
import { getUserByEmail } from '@/lib/db/queries';
import { setSession } from '@/lib/auth/session';

export async function signIn(email: string, password: string) {
  // Get user from database
  const user = await getUserByEmail(email);
  
  if (!user) {
    throw new Error('Invalid email or password');
  }
  
  // Compare provided password with stored hash
  const isValidPassword = await comparePasswords(password, user.passwordHash);
  
  if (!isValidPassword) {
    throw new Error('Invalid email or password');
  }
  
  // Set session if password is valid
  await setSession(user);
  
  return { success: true };
}

Best Practices

Password Requirements

Implement strong password requirements on the client and server:
function validatePassword(password: string): { valid: boolean; error?: string } {
  if (password.length < 8) {
    return { valid: false, error: 'Password must be at least 8 characters' };
  }
  
  if (!/[A-Z]/.test(password)) {
    return { valid: false, error: 'Password must contain an uppercase letter' };
  }
  
  if (!/[a-z]/.test(password)) {
    return { valid: false, error: 'Password must contain a lowercase letter' };
  }
  
  if (!/[0-9]/.test(password)) {
    return { valid: false, error: 'Password must contain a number' };
  }
  
  return { valid: true };
}

Rate Limiting

Implement rate limiting to prevent brute force attacks:
import { Ratelimit } from '@upstash/ratelimit';
import { Redis } from '@upstash/redis';

const ratelimit = new Ratelimit({
  redis: Redis.fromEnv(),
  limiter: Ratelimit.slidingWindow(5, '15 m'), // 5 attempts per 15 minutes
});

export async function signInWithRateLimit(email: string, password: string) {
  const { success } = await ratelimit.limit(email);
  
  if (!success) {
    throw new Error('Too many login attempts. Please try again later.');
  }
  
  // Proceed with authentication
  const isValid = await comparePasswords(password, storedHash);
  // ...
}

Error Messages

Use generic error messages to prevent user enumeration:
// Good: Generic message
throw new Error('Invalid email or password');

// Bad: Reveals whether email exists
throw new Error('Email not found');
throw new Error('Incorrect password');

Build docs developers (and LLMs) love