🔐 Authentication Base: Hashes, Salts, and Sessions
The most dangerous code you will ever write involves User Authentication. If you design a button wrong, the user gets annoyed. If you design Authentication wrong, you will leak 10,000 passwords onto the dark web and destroy your company.
In this lesson, we study the absolute foundational rules of modern password storage: Hashing and Salting.
1️⃣ Rule #1: NEVER Store Plain-Text Passwords
If your database looks like this, you have committed a fireable offense:
| id | username | password |
|---|---|---|
| 1 | admin | Password123 |
| 2 | bob | ilovedogs |
If a hacker exploits a single tiny vulnerability in your Express code (SQL Injection), they download the database table. Because the passwords are raw strings, they instantly log into your users' banking, email, and social media accounts (since people reuse passwords).
2️⃣ The Cryptographic Hash Function
To solve this, we use mathematics. A Hash Function (like SHA-256 or bcrypt) takes an input string and scrambles it into a fixed-length string of garbage.
Input: "hello"
Output (SHA-256): 2CF24DBA5FB0A30E26E83B2AC5B9E29E1B161E5C1FA7425E730...
The Three Laws of Hashing:
- Deterministic: Inputting
"hello"will always generate the exact same hash. - One-Way (Irreversible): It is mathematically impossible to take the hash and reverse-engineer it back into the word
"hello". - Avalanche Effect: Changing
"hello"to"hellp"completely destroys the entire output string, making it impossible to guess if two passwords are similar.
The Authentication Flow:
- User creates account with password
"12345". - Server runs
hash("12345")->abcde99. - Server saves
abcde99to the database. (The server never knows the real password). - User logs in tomorrow and types
"12345". - Server hashes their login attempt ->
abcde99. - Server checks if
abcde99 === abcde99. Access granted.
3️⃣ Breaking Hashes: Rainbow Tables & Salting
Hackers found a way around basic hashing. They generated a "Rainbow Table": a 500GB Excel spreadsheet containing every single word in the dictionary, alongside its pre-computed Hash.
When they steal your database, they just run .find() on their spreadsheet to reverse-lookup the hash. If your user's password was "password123", the hacker cracks it in 0.4 seconds.
The Solution: Salting
A Salt is a 20-character string of pure, random gibberish generated uniquely for every single user at the moment they sign up.
- User registers:
"password123". - Server generates random salt:
XyZ!9kP. - Server glues them together:
"password123" + "XyZ!9kP". - Server hashes the glued string:
8f7b...
Now, the hacker's 500GB Rainbow Table is completely useless, because the dictionary does not contain the word "password123XyZ!9kP".
4️⃣ Implementing Bcrypt in Express
We do not write cryptography from scratch. We rely on industry-standard libraries like Bcrypt, which automatically handles salting and hashing.
npm install bcrypt
Route 1: Registration
import express from 'express';
import bcrypt from 'bcrypt';
const app = express();
app.use(express.json());
app.post('/api/register', async (req, res) => {
const { username, plainTextPassword } = req.body;
try {
// 1. Define the "cost factor" (How slow the math is).
// 10 is standard. By slowing down the CPU, we prevent hackers from
// guessing 10 billion combinations a second via brute force.
const saltRounds = 10;
// 2. Generate the Hash (Bcrypt automatically generates and embeds the Salt inside the hash string)
const hashedPassword = await bcrypt.hash(plainTextPassword, saltRounds);
// 3. Save to DB
await db.users.insert({ username, password: hashedPassword });
res.status(201).json({ message: "Registered safely!" });
} catch (err) {
res.status(500).json({ error: "Server Error" });
}
});
Route 2: Login
app.post('/api/login', async (req, res) => {
const { username, plainTextPasswordAttempt } = req.body;
// 1. Fetch the user's stored hash from the Database
const user = await db.users.find({ username });
if (!user) return res.status(404).json({ error: "User not found" });
// 2. Compare the plain-text attempt against the stored hash
// Bcrypt automatically extracts the embedded salt, applies it to the attempt, hashes it, and compares.
const isMatch = await bcrypt.compare(plainTextPasswordAttempt, user.password);
if (isMatch) {
// SUCCESS!
res.status(200).json({ message: "Logged in!" });
} else {
// Never tell the user "Password incorrect", just say "Invalid Credentials"
// so hackers don't know if they guessed a valid username or not.
res.status(401).json({ error: "Invalid Credentials" });
}
});
💡 Summary Cryptography Rules
| Term | Definition | Purpose |
|---|---|---|
| Plain-Text | my_dog_123 | Never store this. A catastrophic security failure. |
| Encode | Base64 | Just translating letters to other letters. Hackers can trivially reverse this. |
| Encrypt | AES-256 | Scrambling data with a Master Key. You do this if you need to read the data later (like Credit Cards). |
| Hash | SHA-256 | Irreversibly destroying text into a fingerprint. You do this for Passwords. |
| Salt | Random garbage | Added before hashing. Defeats Rainbow Table lookup attacks. |
| Bcrypt | Npm Library | The industry standard algorithm that handles salting and slow-hashing automatically. |