Password Salting And The Throughline Of This Course
This course built an entire security model on top of passwords without ever explaining how one should actually be stored. Time to close that gap, and close the course with it.
The last post closed on a gap this course left open, how a password should actually be stored. Every login flow shown so far assumed a password check just worked, without ever explaining what "storing a password" is supposed to mean. Time to close that.
Hashing: Never Store the Real Thing
A one-way hashing algorithm turns a password into a fixed output. The same input always produces the same output, every single time, which is the property that makes password checking possible without ever keeping the real password around.
const hashedPassword = hash("hunter2");
// always produces the exact same hash for "hunter2"When a user logs in, the server hashes whatever they typed and compares it to the stored hash. It never needs to store, or ever see again, the actual plaintext password, only the hash of it.
If a database ever leaks, an attacker gets the hashed versions, not the real passwords, at least in theory. That "in theory" is doing real work, and it's exactly where the next problem shows up.
Why Hashing Alone Isn't Enough
A rainbow table is a precomputed table mapping common passwords to their hash outputs. Build one from every previous leaked password dataset combined with the usual common-password lists, and it becomes a lookup problem rather than a cracking problem.
If an attacker has a big enough table, they can just look up a leaked hash and instantly recover the original password, without ever needing to break the hashing algorithm itself. Hashing alone protects against reversing the algorithm mathematically. It does nothing against a table built in advance for exactly this purpose.
The Fix: A Salt
A salt is a random, unique-per-user value combined with the password before hashing.
const salt = generateRandomSalt();
const hashedPassword = hash(password + salt);The stored hash now represents "password plus salt," not "password alone." A rainbow table built for plain passwords is useless against it, since the attacker would need an entirely new table for every single unique salt in the database.
That single requirement defeats the entire economic point of a rainbow table. Precomputing a table only pays off because it can be reused across many targets, and a per-user salt takes that reuse away completely.
ExpandA password hashed alone, vulnerable to a precomputed rainbow table lookup, next to the same password combined with a per-user salt before hashing, defeating the table.
Don't Lose Two Things at Once
The same logic that says a signing secret shouldn't live hardcoded next to the thing it protects applies here too. A salt stored in the exact same database row as the hash it protects means one SQL injection, or one database leak, exposes both halves of the protection at once.
Worth being honest that this is a real, non-trivial trade-off to manage, not a solved problem. Splitting a salt away from its hash adds real complexity, and the industry's own answer to that complexity is a category of tool, not a single silver bullet.
Secrets Management, at a Bigger Scale
This course flagged the secret-storage problem earlier and never fully resolved it. Updating a hardcoded or scattered secret across many services and environments by hand doesn't scale, and losing track of exactly who has access to a shared secret is itself a real risk.
Cloud providers each have their own answer to this, storing and rotating shared secrets across multiple services without every service needing to know the underlying value directly. AWS, Azure, and Google Cloud all have their own version, and third-party, cloud-agnostic tools exist too for teams that don't want to lock into one vendor's approach. Which specific tool matters less than the habit it enables: rotate keys periodically, rather than treating a generated secret as something permanent once it's created.
The Throughline
Look back across everything this course has covered, and the same idea keeps showing up wearing different clothes.
- Sanitize input before it ever reaches a dangerous sink.
- Layer a CSP behind that sanitization, in case something slips through.
- Sign and scope cookies correctly, so tampering fails and identity never leaks further than it has to.
- Check origins on both ends of a
postMessage. - Verify a JWT's algorithm, not just its signature.
- Salt a password before it's ever hashed.
None of these is the one fix. Each is one layer in a stack where a mistake in any single layer doesn't have to mean the whole thing falls over.
That's the actual argument for defense in depth, not as a slogan, but as the specific, repeated pattern this entire course kept landing on from a completely different angle each time.
CSP and CORS Aren't the Enemy
CSP and CORS in particular tend to feel like obstacles the first time a developer fights with them. Nothing loads, an error shows up in the console, and the fastest-looking fix is turning the whole thing off.
Once the actual mechanism is understood, though, both of them flip from friction into something closer to an ally, actively working in a developer's favor rather than something to route around. The frustration was never the mechanism being broken. It was not yet understanding what the mechanism was protecting.
A Grounded Close, Not a Triumphant One
Most of the real security failures covered across this course, the plain-text cookies, the missing CSRF tokens, the unsanitized inputs, didn't come from a lack of caring. They came from a gap in understanding, or a decision made under pressure, at three in the morning, with a deadline that didn't leave room to double-check.
The goal of a course like this one was never "unbreakable." Nothing built by people ever fully is, and treating that as the bar just sets everyone up to feel like they failed. The actual goal was building the kind of instinct that notices when something feels a little off, before it turns into an incident, the pause before shipping a cookie without HttpOnly, the second look at a request body being spread straight onto a database record.
That instinct is worth more than memorizing any single fix in this course, because the next vulnerability probably won't look exactly like any of the ones covered here. The pattern underneath it usually will.
The Essentials
- A one-way hash lets a server check a password without ever storing or seeing the real one.
- Rainbow tables defeat hashing alone, using precomputed lookups instead of breaking the algorithm.
- A per-user salt makes precomputed tables useless, since an attacker would need a new table for every unique salt.
- A salt stored next to its hash is still a real risk, the same principle as never hardcoding a secret next to what it protects.
- Secrets management tools solve the update-and-rotate problem at scale, across many services and environments.
- Every control in this course is one layer in a stack, not the single fix. A mistake in one layer shouldn't mean the whole system falls over.
- CSP and CORS feel like obstacles until the mechanism actually clicks, and then they become an ally.
- Most real failures come from a gap in understanding or a rushed decision, not a lack of caring. The real goal is the instinct that catches it early.
Further Reading and Watching
- Rainbow Table Attack and Salting - Explanation & Demonstration: a live demonstration of exactly how a salt defeats a precomputed rainbow table lookup.
- Password Storage Cheat Sheet: OWASP's reference on hashing algorithms, salting, and modern password storage practices.