ASK KNOX
beta
LESSON 249

Authentication Fundamentals — Sessions, Tokens, and Cookies

HTTP is stateless. Sessions, cookies, and tokens are how we fake memory into it. Understanding the mechanics — and the tradeoffs — is the foundation of every auth system you will ever build.

6 min read

HTTP has no memory.

Every request arrives at the server as if it is the first time it has ever seen you. The protocol was designed this way deliberately — statelessness makes servers simpler, more scalable, and easier to reason about. But it creates an immediate problem for anything that requires a persistent user identity: login.

Sessions, cookies, and tokens are the three mechanisms engineers have invented to fake memory into a stateless protocol. Every auth system you will ever build is built on one or more of these three primitives. Before you touch OAuth, PKCE, or JWTs, you need to understand the foundation.

Why HTTP Is Stateless

A web server receives an HTTP request. It processes it. It sends a response. Then it forgets the entire interaction ever happened. The next request — even from the same IP address, even from the same browser — arrives with no context from the previous one.

This design made sense in 1991. Static documents. No user accounts. No shopping carts. No authentication.

The moment the web needed login, engineers needed a way to carry identity across requests. The answer was: put something in the request that lets the server recognize you.

Sessions vs Tokens: Two Patterns

Server-side sessions work by assigning a random identifier (session ID) to the user after login. The server stores the actual user data in its own memory or database, keyed to that session ID. The client receives only the opaque session ID — usually as a cookie. On every subsequent request, the browser sends the cookie, the server looks up the session ID, retrieves the user data, and proceeds.

Tokens (JWTs being the most common) work differently. The entire user payload is encoded into the token itself, signed by the server. The client stores the token and sends it with every request. The server verifies the signature and reads the payload without any database lookup.

The tradeoff is fundamental:

PropertyServer SessionJWT Token
RevocationInstant (delete server record)Hard (must wait for expiry)
ScalabilityRequires shared session store across serversStateless — any server can verify
SizeTiny (just an opaque ID)Larger (full payload in token)
Server stateRequiredNot required
LogoutTrivialRequires token blocklist

Where to Store Auth State: The Decision Table

StorageReadable by JSSent automaticallyPer-originSurvives tab close
Cookie (HttpOnly)NoYes (auto)No (domain-scoped)Yes (if Max-Age set)
Cookie (non-HttpOnly)YesYes (auto)No (domain-scoped)Yes (if Max-Age set)
localStorageYesNo (manual)YesYes
sessionStorageYesNo (manual)YesNo
In-memory (React state)YesNo (manual)YesNo

Cookies are sent automatically by the browser on every matching request. They support server-side access (with HttpOnly) and cross-subdomain sharing. They are the correct choice for server-rendered applications.

localStorage is persistent, per-origin storage that lives in the browser. It is convenient for client-side SPAs but it is completely inaccessible to subdomains on different origins — www.jeremyknox.ai and academy.jeremyknox.ai each have their own isolated localStorage.

Cookie Attributes — Every One Matters

Set-Cookie: session=abc123;
  HttpOnly;
  Secure;
  SameSite=Lax;
  Domain=.jeremyknox.ai;
  Path=/;
  Max-Age=86400

HttpOnly — Prevents JavaScript from reading the cookie via document.cookie. Essential protection against XSS attacks stealing your session token.

Secure — Cookie is only transmitted over HTTPS. Never omit this in production.

SameSite — Controls when cookies are sent in cross-site requests:

  • Strict: Cookie sent only on same-site requests. Never on cross-site navigations. Maximum security, but breaks OAuth redirects.
  • Lax: Cookie sent on same-site requests AND top-level navigations (clicking a link). Default in modern browsers. Correct for most apps.
  • None: Cookie sent in all contexts, including cross-site iframes and AJAX. Requires Secure. Needed for embedded widgets and cross-domain cookie sharing.

Domain — Which domains receive the cookie. Domain=.jeremyknox.ai (note the leading dot) makes the cookie available to all subdomains: www.jeremyknox.ai, academy.jeremyknox.ai, api.jeremyknox.ai. Without the Domain attribute, the cookie is scoped to the exact hostname that set it.

Path — Which URL paths receive the cookie. Usually / for auth cookies.

Max-Age (or Expires) — How long the cookie lives. Without this, it is a session cookie — deleted when the browser closes.

Domain Scoping: The Key Insight

This distinction trips up almost every engineer building multi-subdomain auth for the first time.

# Set by: academy.jeremyknox.ai
# WITHOUT domain attribute:
Set-Cookie: session=abc123; HttpOnly; Secure

# Result: cookie ONLY sent to academy.jeremyknox.ai
# www.jeremyknox.ai does NOT receive it
# api.jeremyknox.ai does NOT receive it

# WITH domain attribute:
Set-Cookie: session=abc123; HttpOnly; Secure; Domain=.jeremyknox.ai

# Result: cookie sent to ALL subdomains
# www.jeremyknox.ai ✓
# academy.jeremyknox.ai ✓
# api.jeremyknox.ai ✓

Case Study: The localStorage Mistake

In early April 2026, the jeremyknox.ai ecosystem ran into exactly this problem. The main site (www.jeremyknox.ai) implemented user authentication using Supabase's default client, which stores the auth token in localStorage under the key sb-{project-ref}-auth-token.

When the Academy (academy.jeremyknox.ai) was deployed as a separate Vercel project, the assumption was that users who logged in on www would also be logged in on academy. After all, they were the same logical application.

They were not. localStorage is per-origin. www.jeremyknox.ai and academy.jeremyknox.ai are different origins. The auth token sitting in localStorage on www was completely invisible to academy. Every user who navigated from the main site to the academy saw a login prompt.

The fix was not a configuration tweak. It required a fundamental architecture change — moving from localStorage to cross-domain cookies. That architecture (and the 12 PRs it took to get there) is the running case study throughout this track.

Lesson 1 Drill

Open your browser's DevTools on any site you use daily. Go to Application → Cookies. Find the authentication cookie. Read its attributes:

  1. Is it HttpOnly? (You cannot see the value if so — the column shows the name but DevTools shows [HttpOnly] in a separate column)
  2. Is it Secure?
  3. What is the Domain? Does it start with a dot?
  4. What is SameSite set to?
  5. Is there a Max-Age or Expires?

Then find the same site's localStorage. Look for any tokens stored there. Ask yourself: if this site had a subdomain, would the localStorage token be visible there?

You have just done your first auth security audit.