Authentication & Authorization — Fundamentals
Password hashing — why plaintext and encryption are both wrong
Storing a password as plaintext means anyone with database access — an attacker after a breach, a careless insider — has every user's actual password directly. Encrypting it is better but still wrong for this purpose: encryption is designed to be reversible with the right key, and if an attacker compromises the server, they very likely have access to that key too (it has to live somewhere the server can reach it).
Hashing is the correct tool because it's deliberately one-way — there is no key that turns a hash back into the original password. bcrypt and argon2 are the real, correct answers here specifically because they're also deliberately slow — an attacker who steals a database of hashes can't brute-force millions of guesses per second the way they could against a fast hash like plain SHA-256.
Sessions and cookies — server-side identity state
A session is a record the server keeps (in memory for a toy example, Redis or a database in production) mapping an opaque session ID to "this is user 42, logged in at this time." The client only ever holds that opaque ID, delivered via a cookie the browser automatically re-attaches to every request to the same domain.
Revoking a session is trivial — delete that one record server-side (req.session.destroy()), and the cookie the client still holds is now useless, since the server no longer recognizes it.
JWTs — structure, and "signed, not encrypted"
A JWT is three base64 segments joined by dots: header.payload.signature. The header names the signing algorithm; the payload carries claims (arbitrary data — a user ID, a role, an expiry); the signature is computed from the header and payload using a secret key, and lets a verifier confirm the token wasn't tampered with since issuance.
This is a genuinely common misconception worth stating directly: a standard JWT's payload is not encrypted — it's just base64-encoded, which is trivially reversible by anyone, not a security mechanism at all. Paste any JWT into jwt.io and its payload decodes instantly, no secret required. The signature proves integrity (nothing was changed) and authenticity (it was issued by whoever holds the secret) — it says nothing about confidentiality. Never put a password, a credit card number, or anything genuinely secret directly into a JWT payload.
Middleware-based route protection in Express
Auth checks belong in middleware, not duplicated inside every route handler — this is exactly the cross-cutting-concern pattern this academy's Express material already establishes for logging and validation.
Note the same next() contract from this academy's Express Fundamentals — requireAuth either calls next() (identity confirmed, continue) or sends a response itself (401), never both, never neither.
Basic role-based authorization
Authentication middleware answers "who is this." A separate, second middleware answers "is this identity allowed to do this specific thing" — the two checks stay distinct on purpose.
401 vs 403 is a real, meaningful distinction, not interchangeable: 401 means "I don't know who you are" (missing/invalid token); 403 means "I know exactly who you are, and the answer is no."
Try It (2 Minutes)
Run it twice with the same password and notice the hash is different each time (bcrypt generates a random salt per call) — yet compare still correctly returns true both times. That's the salt embedded in the stored hash doing its job.

