Warming up the neural circuits...
By the end of this chapter you will:
Auth is where most products get hacked. Not from clever attacks — from boring mistakes. Get the basics right and you'll never appear on Have-I-Been-Pwned.
You walk into a hotel. The front desk checks your ID and credit card and gives you a keycard. The keycard works on your room and the gym; it doesn't work on the bar, the kitchen, or other rooms.
The ID check was authentication ("who are you?"). The keycard is your session — a token that proves who you are without showing your ID every door. The fact that it works on your room but not the bar is authorization ("what may you do?").
The keycard can be cloned (token theft) or expire (session timeout) or be deactivated when you check out (logout). All three exist in software too.
These two words look alike. They are not.
Most "auth bugs" are actually authz bugs. The user is who they say they are — they just shouldn't be able to do that. We'll spend much of L3 on authz.
This chapter is about authn.
There are exactly two ways to remember a logged-in user across requests. Everything else is a variant.
abc123...) and stores it in a database / Redis with the user's ID.Set-Cookie: session_id=abc123; HttpOnly; Secure; SameSite=Lax.abc123 → finds user 42 → authenticated.Rendering diagram…
Good. Easy to revoke (delete from store). Secrets stay server-side. Battle-tested.
Bad. Requires a shared session store across all your servers. Harder for mobile/native (no automatic cookies).
{"sub": 42, "exp": ...}.Authorization: Bearer <token> on each request.Good. Stateless — any server can verify. Works easily for mobile and service-to-service.
Bad. Hard to revoke before expiry (need a denylist, defeating the point). Tokens leak via XSS, logs, third-party scripts.
Most products end up doing both: short-lived access token (JWT) + long-lived refresh token (session-backed). We'll build this in L3.
A JWT is three base64url-encoded chunks separated by dots:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJzdWIiOiI0MiIsImV4cCI6MTk5OTk5OTk5OX0.
SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5cDecoded:
{ "alg": "HS256", "typ": "JWT" }{ "sub": "42", "exp": 1999999999 }Signature: HMAC-SHA256(header.payload, secret)Critical things to know:
alg: none is a real, dangerous attack. Always specify the expected algorithm when verifying.$ echo "eyJzdWIiOiI0MiJ9" | base64 -d
{"sub":"42"}That's how easy it is to read a JWT. Treat the payload as public information that's also tamper-evident.
We'll go deep in L3. The basics here:
import bcrypt from 'bcrypt';
// On signup
const hash = await bcrypt.hash(password, 12); // cost 12 = ~250ms on modern hardware
await db.users.create({ email
You won't always do auth yourself. OAuth lets Google, GitHub, Apple, etc. authenticate the user for you.
Rendering diagram…
We'll wire this up in L3. For now know it exists, that you should never roll OAuth yourself (use a library), and that PKCE is mandatory for any client you don't control.
| Mistake | Why it's wrong | What to do |
|---|---|---|
| MD5/SHA1 for passwords | Cracked at billions/second on a GPU | bcrypt or argon2 |
| JWT in localStorage | XSS reads it instantly | HttpOnly cookie, or in-memory only |
| Long-lived JWTs without refresh | Can't be revoked | Short access token + refresh token |
| Same secret for signing JWTs everywhere | One leak compromises everything | Per-environment secrets, rotate regularly |
Comparing tokens/hashes with === | Timing attacks | Use a constant-time comparison (crypto.timingSafeEqual) |
Production. Log every auth event (login, logout, password change). Monitor failure rates — a spike means a credential stuffing attack.
Performance. Sessions backed by Redis are fast. Don't put session lookups behind your slow primary DB.
Security. Set cookies with HttpOnly; Secure; SameSite=Lax by default. Make exceptions only when you know why.
HttpOnly, Secure, SameSite.sub and re-signing with your own secret. Verify the change works.Beginner. What's the difference between authentication and authorization? Authn = who you are. Authz = what you may do.
Senior. How do you revoke a JWT? You can't, by design. Workarounds: short TTL + refresh tokens; or keep a denylist (which defeats statelessness); or pair JWT with a session for sensitive ops.
Authentication is "who are you?" Two mechanisms dominate: sessions (cookies + server store) and tokens (JWT + signature). Sessions are simpler and easier to revoke; tokens are stateless and scale better. Either way: hash passwords with bcrypt/argon2, set cookies correctly, and never trust the client.
HttpOnly on a cookie prevent?alg: none attack?