hashPassword()
Hashes a plain text password using bcrypt with a salt round of 10.
The plain text password to hash
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
User Registration
Password Update
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.
The plain text password to verify (e.g., from user input)
The hashed password to compare against (e.g., from the database)
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
Sign In Authentication
Password Verification
Password Change Flow
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' );