auth · Lesson 1

How an App Remembers You're Logged In

HTTP forgets you after every request. So every auth system — passwords, OAuth, magic links, passkeys — exists to answer one question: once you've proven who you are, how does the server recognize you on the next request? There are only two answers. Get this and the whole field snaps into a grid.

🎯 Mission: a mental model of modern auth + the ability to wire it into a real app
The one idea

After you log in, the server hands your browser a credential it sends back on every later request. That credential is either a session ID (a meaningless ticket — the server holds the real data) or a token (a self-contained, signed pass — the server holds nothing). Stateful vs stateless. That single fork — where does the truth live, server-side or in the credential itself — is the spine of every auth system you'll ever read.

01 Why this is the keystone

HTTP is stateless: each request arrives with no memory of the last one. Logging in is just one request that proves identity. The hard part is the millions of requests after it — the server must re-recognize you without making you re-type a password each time.

So split every auth system into two phases. Authentication (the login event — "prove it's you") is where passwords, OAuth, magic links and passkeys differ wildly. But they all converge on the same second phase: the session (staying logged in), and there you have only two designs.

💡 The vocabulary you'll reuse all course Authentication (authN) = who are you? (login). Authorization (authZ) = what are you allowed to do? (permissions). They're constantly confused — even "OAuth" is named for authorization but is mostly used for authentication. See MDN — Web Security.

Here's the move that makes auth click: OAuth2, JWT, passwordless, passkeys aren't competing alternatives on the same axis. They live on different phases. OAuth is a way to do the login; a session ID or token is how you stay logged in afterward. This lesson owns the second phase so the rest of the course can focus on the first.

02 The shape of every logged-in request

Whatever the login method, the steady state looks identical. You hold a credential; you attach it to each request; the server checks it and decides if it knows you.

Your browser holds the credential Server verifies it ① POST /login (password / OAuth / magic link / passkey) this phase differs per method — the rest of the course ② Set-Cookie: a session ID — or — here's your token ③ GET /account + credential attached automatically ④ 200 OK — "I recognize you" (repeat for every request)
Steps ①–② vary by login method. Steps ③–④ — the steady state — have only two designs. This lesson is about ③–④.

The credential at step ② is almost always carried in a cookie (sent automatically by the browser) or an Authorization header (attached manually by JS / an app). Which carrier you use is a separate choice from which kind of credential it is — we'll untangle that next lesson. For now: focus on what's inside the credential.

03 Fork in the road: stateful vs stateless

Option A — Session ID (stateful): the server remembers

On login the server creates a record — { session: "a1b2c3…", user: 42, expires: … } — in its own store (memory, Redis, a DB). It sends back only the random ID. The ID is a meaningless ticket: it carries no information, it just points to the server's record. Every request, the server looks up the ID to find out who you are.

# What the browser receives — just an opaque pointer:
Set-Cookie: sid=8f4e2a91c7b6d05f; HttpOnly; Secure; SameSite=Lax

# What the SERVER stores (the real truth):
sessions["8f4e2a91c7b6d05f"] = { user_id: 42, role: "admin", exp: 1718900000 }

Option B — Token (stateless): the credential is the data

On login the server builds a credential that contains your identity (user 42, role admin, expires …) and cryptographically signs it. It stores nothing. On each request it just verifies the signature — if the math checks out, the token is genuine and unaltered, so the server trusts the claims inside it. The most common format is a JWT (JSON Web Token — a whole future lesson).

# A token (JWT) — three base64 parts: header.payload.signature
Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJ1c2VyIjo0Miwicm9sZSI6ImFkbWluIn0.k3rR…sig
                       └ header ┘ └──────── payload (readable!) ────────┘ └ signature ┘
# Server stores: NOTHING. It re-checks the signature with its secret key each time.
⚠️ The #1 token misconception A signed token is not encrypted — the payload is just base64, anyone can read it (paste one into jwt.io and see). Signing proves it wasn't tampered with, not that it's secret. Never put anything private in a JWT payload. See OWASP JWT Cheat Sheet.

04 The trade-off that decides everything

Because one design keeps the truth on the server and the other puts it in the credential, every difference between them flows from that. The big one: revocation. A session is killed instantly — delete the server record and the ticket is dead. A token can't be un-signed; it's valid until it expires, which is why tokens are kept short-lived and paired with a "refresh" mechanism (a later lesson).

QuestionSession ID (stateful)Token / JWT (stateless)
Where's the truth?Server store (Redis/DB)Inside the credential
Server stores per-user?Yes — a row per sessionNo — verifies a signature
Log out / ban instantly?Yes — delete the recordHard — valid until expiry (need a denylist)
Scales across many servers?Needs shared storeEasy — any server can verify
Works great for…Classic single-domain web appsAPIs, mobile, microservices, cross-domain
Usually carried in…A cookie (automatic)Authorization: Bearer header
Size on the wireTiny (an ID)Bigger (carries all the claims)
💡 The honest default For a normal server-rendered web app on one domain, session IDs in an httpOnly cookie are the boring, safe, recommended choice — instant logout, nothing leaks to JS. Reach for tokens when you genuinely need statelessness: a public API, a mobile client, or services that can't share a session store. "JWT for browser login" is a famous over-reach. See OWASP Session Management + "Don't use JWTs for sessions".

05 Where the rest of the course lives on this map

Now place the scary words. Notice they're not alternatives to each other — they sit on different phases:

Passwords / OAuth / Magic links / Passkeys

All are login methods — phase ①. They differ in how you prove identity, then they all hand you a session or token.

Session ID vs JWT

The two answers to "stay logged in" — phase ③. This lesson. Everything else plugs into one of these.

OAuth2 / OIDC

A protocol to get a token from another service ("Sign in with Google"). It's a fancy way to obtain the credential in this lesson.

Cookies, httpOnly, SameSite, CSRF

How the credential is carried and protected in the browser. Next lesson.

06 🎯 Your tangible win (3 min): find your own credential

You already have one of these in your browser right now. Let's look at it.

  1. Open a site you're logged into (GitHub, Gmail, anything) in Chrome.
  2. Open DevTools: ⌥⌘I → Application tab → Cookies → pick the site's domain.
  3. Look for a cookie whose value is a long opaque string. Check its flags columns: HttpOnly ✓, Secure ✓, SameSite.
🎯 The win + how to tell which kind you're holding That cookie is the credential from step ②. Now diagnose it: does the value have two dots splitting it into three chunks (xxx.yyy.zzz)? That's a JWT — copy the middle chunk into jwt.io and you'll read your own user id. Just one opaque blob with no dots? That's almost certainly a session ID — meaningless to you, a lookup key to them. You just classified a real-world auth system using only this lesson. (Most big sites use session IDs in httpOnly cookies — exactly the "boring safe default" above.)

Tip: an HttpOnly cookie is invisible to document.cookie in the JS console — that's the point, it's why XSS can't steal it. You can only see it in the Application tab.

07 Check yourself

Q1. A coworker says "we use JWTs so we don't have to store sessions in the database — but we also need to ban abusive users instantly." What's the tension?

Show answer

Statelessness and instant revocation are in direct conflict. A signed JWT is valid until it expires no matter what the server wants — there's no record to delete. To ban instantly you must add server state back (a token denylist / short expiry + refresh), which partly gives up the "no storage" benefit they wanted. This trade-off is the heart of §04.

Q2. True or false: a JWT is safe to put a user's private data in, because it's signed.

Show answer

False. Signed ≠ encrypted. The payload is base64-encoded plaintext that anyone holding the token can read. Signing only guarantees it wasn't altered. Private data does not belong in a JWT payload (§03 warning).

Q3. Where does "Sign in with Google" fit — is it an alternative to sessions/tokens?

Show answer

No — it's a login method (phase ①). OAuth/OIDC is how you prove identity via Google; after it succeeds your app still issues you a session ID or token to stay logged in (phase ③). They live on different phases, not in competition. (§05.)

08 Where we go next

You now have the spine. Next lesson: Cookies done right — the carrier almost everything rides in. We'll make HttpOnly, Secure, and especially SameSite concrete, and see exactly how a CSRF attack works and why one cookie flag mostly kills it.

📌 Remember this one line Every auth system = a way to log in (varies wildly) + a way to stay logged in (only two: stateful session ID or stateless signed token).

💬 I'm your teacher for this — ask me anything that's fuzzy. Good questions to throw back at me: "show me a real Express/FastAPI example of each", "what's a refresh token then?", or "which should I use for the app I'm building?" (tell me the stack and I'll ground the next lesson in it). · See also: course resources.