Foundations · Lesson 4/8

Auth & Security — Who Are You?

35 min 2 exercises 1 quiz

What you'll learn

  • Authentication vs Authorization (two different questions)
  • Why you must never store passwords as plain text
  • What hashing and tokens (JWT) are
  • You'll write a login-attempt checker

Two doors: ID check and permission slip 🔑

Two words sound alike but ask different questions:

  • Authentication = "Who are you?" — proving identity (username + password, or GitHub login).
  • Authorization = "What are you allowed to do?" — a logged-in user can read posts; only an admin can delete them.

Authenticate first, then authorize.

The #1 rule: never store passwords as plain text. If your database leaks (and databases leak), every password is exposed — and people reuse passwords everywhere. Instead you store a hash.

A hash is a one-way blender: it turns "pizza123" into something like a1b2c3.... Same input always gives the same hash, but you cannot reverse it back to the password. So at login you hash what the user typed and compare it to the stored hash — you never store or even see the real password. (Real systems use bcrypt/argon2, which also add a random "salt" so identical passwords get different hashes.)

Tokens (JWT). HTTP forgets you after every request. So after you log in, the server hands you a signed token (a JWT — JSON Web Token). Your browser sends it with each request, like a wristband at an event: the server checks the signature and knows it's really you, without asking for your password again. This very site does exactly that — a JWT in your browser keeps you signed in.

Exercise 1

Simulate a safe login check. You have a stored hash (pretend hashing = reversing the string, just for practice). Write check_login(stored_hash, typed_password) that hashes the typed password the same way and returns True only if it matches.

Use this fake hash function: fake_hash(pw) = the password reversed (pw[::-1]).

Sessions, refresh, and revocation — the loose ends 🧵

A signed JWT proves "this really is Sara" — but two practical questions follow immediately: how long should that proof last, and what happens if you need to cancel it early?

Short-lived tokens + refresh tokens. If a JWT lasted forever, a stolen token would grant forever-access. So real systems issue a short-lived access token (minutes to hours) for everyday requests, plus a longer-lived refresh token stored more carefully (often an HttpOnly cookie) that's only used to silently mint a new access token when the old one expires. The user never notices — they just stay logged in — but a leaked access token expires quickly on its own.

The revocation problem. Here's the catch with JWTs: once signed, the server can't "unsign" one. If a token isn't expired yet, it stays valid everywhere it's presented — even if the user logs out or an admin bans them. Two common fixes:

  • Keep tokens very short-lived, so a "logout" mostly just means "stop sending the refresh token."
  • Maintain a small server-side denylist (recently revoked token IDs) checked on top of signature verification, until expiry catches up.

Sessions vs tokens, in one line: a classic session keeps the source of truth in server memory/DB (a session ID cookie), so logging out is instant — but it doesn't scale across many servers as easily. A JWT carries its own proof and scales beautifully across many servers, at the cost of exactly this revocation awkwardness. Neither is "wrong" — it's a trade-off, like most of backend engineering.

Exercise 2

Simulate authentication AND authorization together. Write canDeletePost(user, post) that returns true only if the user is authenticated (not null) AND is authorized — either they own the post (user.id === post.ownerId) or their role is "admin".

This mirrors real middleware: check identity first, then permission.

Real-world case study: this platform's actual login flow 🔍

Everything in this lesson is exactly how you logged into this very site. Its Worker (worker/src/index.ts) implements GitHub OAuth end to end. When you click "Sign in," handleAuthGitHub generates a random state value, stores it in an HttpOnly cookie, and redirects you to GitHub — that state round-trip is CSRF protection: it proves the callback that comes back a moment later actually started from this site, not from an attacker's forged link.

After you approve on GitHub, handleAuthCallback exchanges the OAuth code for an access token, fetches your GitHub profile, and upserts you into the users table with a single SQL statement (ON CONFLICT(github_id) DO UPDATE ...) — the same insert-or-update pattern you'd design yourself for a first-time-vs-returning user. Only then does it call signJWT, which builds a token good for 30 days and signs it with HMAC-SHA256 using crypto.subtle — no third-party auth library, just the Web Crypto API this lesson's hashing concepts are built on.

Notice one deliberate, non-obvious detail: the finished token is appended to the redirect URL as a fragment (#token=...), not a query parameter (?token=...). Fragments never get sent to any server, never appear in server access logs, and never leak via the Referer header when the page loads another resource — a small choice that meaningfully reduces where a token could accidentally leak. On every later request, getUser reads the Authorization: Bearer <token> header and calls verifyJWT, which re-checks the HMAC signature and explicitly rejects any token missing an exp field or past its expiry — exactly the "authenticate first" step this lesson describes, running for real, on every single API call this platform serves.

Quick quiz

Question 1 of 5

Authentication answers which question?

Question 2 of 5

Why store a password hash instead of the password?

Question 3 of 5

What is a JWT used for?

Question 4 of 5

Why do real systems pair a short-lived access token with a refresh token?

Question 5 of 5

Why does this platform put the JWT in the redirect URL's fragment (#token=...) instead of a query parameter?

Summary

  • Authentication (who?) then Authorization (allowed to?)
  • Never store plain passwords — store one-way hashes (bcrypt/argon2 + salt)
  • JWT tokens keep users logged in without re-sending passwords
  • You built a hash-based login check, then an authenticate-then-authorize permission check
  • Short-lived access tokens + refresh tokens balance security with staying logged in; revocation is the classic JWT trade-off
  • This platform's real GitHub OAuth flow does all of this — CSRF-safe state, HMAC-signed JWTs, and a fragment-based token handoff

Security note: you learned this properly — the same principles secure this very platform. Next: Node.js, a popular runtime for building these backends.