Password hashing with bcrypt
Your bookstore API has real data now. Then you check the logs and notice someone just ran a curl command to delete your entire book catalog. No login, no token, no friction. The vault is perfect, the door is wide open. Auth is what closes the door.
First rule: never store a password. If your database ever leaks, and databases do leak, every plain-text password leaks with it. Users reuse passwords. Your leak becomes their whole identity. You store a hash, which is a one-way mathematical function. You can verify a password against the hash, but you cannot reverse it.
No. SHA and MD5 are designed to be fast. That is the opposite of what you want for passwords. bcrypt is designed to be slow on purpose. Each hash takes about 100 milliseconds, which makes brute-force attacks impractical. Fast hashes are for checksums. Slow hashes are for passwords.
import { pgTable, serial, varchar, timestamp } from 'drizzle-orm/pg-core';
export const users = pgTable('users', {
id: serial('id').primaryKey(),
username: varchar('username', { length: 255 }).notNull().unique(),
passwordHash: varchar('password_hash', { length: 255 }).notNull(),
role: varchar('role', { length: 50 }).notNull().default('customer'),
createdAt: timestamp('created_at').defaultNow().notNull(),
});
export type User = typeof users.$inferSelect;
export type NewUser = typeof users.$inferInsert;The bookstore schema grows a users table. Note the column is passwordHash, never password.
Same pattern as the books table: define it in schema.ts, push it with drizzle-kit, and the User type comes free from inference. The column name alone documents the rule we are about to enforce: only a hash is ever stored.
import bcrypt from 'bcrypt';
import { db } from '../db';
import { users, User } from '../db/schema';
export async function createUser(
username: string,
plainTextPassword: string,
role: 'admin' | 'customer' = 'customer',
): Promise<Omit<User, 'passwordHash'>> {
const saltRounds = 10;
const passwordHash = await bcrypt.hash(plainTextPassword, saltRounds);
const result = await db
.insert(users)
.values({ username, passwordHash, role })
.returning();
const user = result[0];
const { passwordHash: _, ...safeUser } = user;
return safeUser;
}Hash the password before insert, destructure the hash out of the response.
Look at the last few lines. After the insert, the service destructures passwordHash out of the result before returning. This is the habit that keeps hashes out of API responses. Hashes can still be attacked offline, so they never leave the backend. Ever.
Quiz: Quiz
Loading practiceโฆ