🎯 The framing that fixes the debate 0:00
The video opens on the argument every developer has seen: someone claims JWTs are the modern answer, someone else insists sessions are more secure, and OAuth gets mentioned whenever a login button says Google. His correction is the whole premise, and it is right:
What follows is a genuinely careful explainer — no hype, no "stateless is better" slogan, and several cautions that most tutorials get wrong. Below is his argument, then the parts that matter and are missing.
🔤 Authentication versus authorization 1:25
| Concept | Question | Example |
|---|---|---|
| Authentication | Who are you? | Email and password, verified at login |
| Authorization | What are you allowed to do? | A user sees their profile; an admin manages all users |
Then the mapping that kills most of the confusion:
- Sessions and JWTs are ways to maintain or represent an authenticated state.
- OAuth is an authorization framework — it lets an application obtain limited access to resources with the user's authorization, without ever seeing the user's password.
- OpenID Connect is the protocol you actually use for "Sign in with Google": an identity layer built on top of OAuth 2.0.
🍪 Sessions: server state, cookie identifier 2:40
The flow in four steps, and the emphasis is on where the data lives:
| Step | What happens |
|---|---|
| 1 | The user submits credentials |
| 2 | The server verifies them and creates a session — stored in a database, Redis, or another session store |
| 3 | The server sends a session ID to the browser in a cookie |
| 4 | The browser sends that cookie with subsequent requests; the server looks up the session and knows who you are |
The point worth internalising: the session data lives on the server; the browser holds only an identifier. Everything that follows — revocation being trivial, scaling needing a shared store — is a consequence of that one decision.
HttpOnly (JavaScript cannot read it, which blunts XSS token theft), Secure (HTTPS only), and an appropriate SameSite policy (which is what limits cross-site request forgery), plus HTTPS, session-ID rotation after login or privilege change, and CSRF defences. A session cookie without these is a bearer token with extra steps.
✅ Session strengths, and the scaling objection defused 3:00
Three advantages: they are easy to reason about, they are easy to revoke (lose your laptop, invalidate the session server-side and subsequent requests fail), and they fit server-rendered applications without building a token-management system.
The usual objection is scaling: multiple servers need shared session state. His answer is refreshingly undramatic — a shared session store is "a well-understood and widely used solution", not a fatal flaw. That is the correct weighting, and it is where most "JWTs scale better" arguments quietly assume that Redis does not exist.
🧾 JWT anatomy: signed is not encrypted 5:42
A JWT is three Base64URL-encoded parts separated by dots: header (token type and signing algorithm), payload (claims — subject, issuer, expiry, permissions), and signature (proof it was signed by a trusted party and not altered).
This is the distinction between JWS (signing, RFC 7519) and JWE (encryption). Most JWTs you meet are JWS: readable by anyone, tamper-evident to those who check the signature.
🔑 The token flow and what a verifier must check 6:51
Login issues an access token; the app stores it; each API call carries Authorization: Bearer <token>. The API then validates:
| Check | What it prevents |
|---|---|
| Signature | Forged or altered tokens |
Issuer (iss) | Tokens minted by a different system |
Audience (aud) | Tokens issued for a different service being replayed against yours |
Expiry (exp, nbf) | Old tokens, clock-window abuse |
| Scopes / permissions | Authenticated but not authorized actions |
He is also explicit that a JWT is "a token format, not a complete authentication system" — it does not give you login, logout, refresh, account recovery or authorization for free. And that statelessness is not automatic speed: validation, key management, revocation and authorization checks all cost something.
⏳ Revocation is JWT's real cost 8:43
The core problem, stated precisely: if a token is valid for an hour and the user logs out, deleting it from the client does not invalidate a copy an attacker already has. It stays usable until it expires.
| Mitigation | Effect | Cost |
|---|---|---|
| Short-lived access tokens | Narrows the window a stolen token works | More refreshes, more round trips |
| Refresh tokens with rotation | Long sessions without long-lived access tokens | Rotation logic, secure storage |
| Reuse detection | Detects a stolen refresh token being replayed | Server-side state per token family |
| Denylist / token version | Immediate revocation for specific tokens or users | Reintroduces the per-request lookup you were avoiding |
🗄 Where to store the token, and which threat you bought 9:24
The trade-off, stated cleanly:
| Storage | Exposure | New risk introduced |
|---|---|---|
localStorage | Any JavaScript on the page can read it — one XSS is full token theft | — |
| Cookie | Not readable by JS if HttpOnly | Sent automatically, so CSRF becomes relevant again |
| OS secure storage (mobile) | Isolated from app code | Platform-specific APIs |
The framing is exact: putting a token in a cookie "changes the threat model, but it doesn't eliminate every risk". XSS and CSRF are different attacks with different defences, and moving the token between storage locations swaps which one you must handle.
🤝 OAuth: delegated authorization, not login 10:08
The example is the clearest way to explain it: a calendar app that needs to read your Google Calendar. You do not hand over your Google password, and the app must never store it. Instead the app redirects to Google's authorization service, the user reviews the requested permissions, and Google issues an access token limited to those scopes.
| Concept | Purpose |
|---|---|
| OAuth 2.0 access token | Access to protected resources — not proof of identity |
| OIDC ID token | Authentication claims about the user, for the client it was issued to |
| Authorization code flow | The central pattern for web, mobile and native apps |
| PKCE (RFC 7636) | Stops an intercepted authorization code from being redeemed by an attacker |
He lists the correct hygiene: PKCE for public clients (browser apps, native mobile), a proper confidential-client setup where a secret can genuinely be kept, HTTPS everywhere, registered redirect URIs, and validated protocol parameters — plus the closing warning that OAuth's security depends entirely on implementing it correctly.
🆔 OIDC: the identity layer, and two different tokens 11:15
OpenID Connect builds on OAuth 2.0 and adds the identity layer: how a client requests authentication and receives information about the authenticated user, including an ID token in common flows. The distinction he draws — access token for resources, ID token for identity, each for a specific audience — is the one that prevents a whole category of confused-deputy bugs.
📊 Side by side 12:57
| Sessions | JWT | OAuth / OIDC | |
|---|---|---|---|
| State lives | Server, browser holds an ID | Client holds a signed token | Authorization server issues tokens; types vary |
| Logout / revocation | Immediate | Needs extra mechanism | Depends on token type and provider |
| Scaling | Shared store | Many services verify the same issuer | Designed for cross-service delegation |
| Solves | Login state for one app | Portable verified claims | Delegated access; identity via OIDC |
| Wrong tool when | You need cross-service portable claims | You need instant revocation without state | You want "login" from bare OAuth |
🧭 Four scenarios, four defaults 16:03
| Scenario | Default | Non-negotiables |
|---|---|---|
| Traditional server-rendered web app | Server-side sessions + secure cookies | HTTPS, HttpOnly/Secure/SameSite, regenerate session ID on login and privilege change, CSRF defence, invalidate on logout |
| Mobile app calling an API | Short-lived access token + refresh token | OS secure storage; no client secrets in the app; opaque tokens are a valid choice when introspection is needed |
| Multiple backend services | JWT access token from a trusted issuer | Validate signature, iss, aud, exp, scopes; do not forward the same token to every service; each service enforces its own authorization |
| Sign in with Google | OIDC for login, then your own session or token for your app | Validate signature, issuer, audience, time claims |
That last row is the one that reframes everything: OIDC handles the external identity flow, while sessions or tokens handle access to your own application. The technologies compose — they do not compete.
🚫 Five mistakes worth avoiding 19:55
- Assuming JWTs are more secure than sessions. Security lives in implementation, storage, expiry, key management and revocation.
- Assuming JWT payloads are secret. A signed token is readable by whoever holds it.
- Using OAuth access tokens as login proof. Use OIDC for authentication through an identity provider.
- Forgetting token theft and logout. Deleting a client-side copy does not invalidate a stolen one — design expiry, revocation, rotation and session invalidation deliberately.
- Storing credentials carelessly. Protect cookies, keep bearer tokens away from unnecessary JavaScript, use secure storage — and remember XSS and CSRF are different threats.
Then the closing rule that should be printed in every team's README: do not implement your own cryptographic protocol. Use mature frameworks, trusted identity providers and established libraries.
🧩 What the video leaves out 23:00
The explainer is accurate but conservative: it describes the durable fundamentals and skips several things that decide real incidents. These are additions, verified against the standards:
| Gap | Why it matters |
|---|---|
Algorithm confusion / alg: none | A verifier that trusts the token's own header can be tricked into skipping signature checks, or into verifying an RSA token as if it were HMAC using the public key as the secret. Always pin the accepted algorithms; never let the token choose. |
| Key rotation and JWKS | Signing keys must rotate, and verifiers fetch the current set from the issuer's published JWKS and select by kid. "Validate the signature" is not implementable without a key-management story. |
| RFC 9700, OAuth 2.0 Security BCP | The 2025 best-current-practice document: PKCE required for all clients (not just public), the implicit flow prohibited, no bearer tokens in URLs, and guidance on sender-constrained tokens. |
| Sender-constrained tokens (DPoP, RFC 9449) | Binds a token to a client key, so a stolen bearer token is not automatically replayable. The natural answer to the theft problem the video describes without naming the mitigation. |
| The BFF pattern | For single-page apps, the modern default is a backend-for-frontend that holds tokens server-side and gives the browser an HttpOnly session cookie. It gets you out of the localStorage-versus-cookie dilemma entirely rather than picking a side. |
| Passkeys / WebAuthn | Conspicuously absent from a 2026 authentication video. Passkeys change the authentication step itself (phishing-resistant, no shared secret) while leaving the session-versus-token question exactly as it was. |
| Enterprise SSO | SAML 2.0 still dominates enterprise identity; OIDC is the modern counterpart. If you sell to companies, "Sign in with Google" is a consumer feature, not an SSO story. |
HttpOnly (he says "HTTP-only"), and at 8:33 the transcript's "maintaining a day list" is a denylist. Neither changes the argument; both matter if you go looking for the setting in your framework.
🤖 Where this lands in 2026: agents and MCP 23:00
The video is aimed at web developers, but the same three primitives are now the vocabulary of agent infrastructure — and the analogy is exact enough to be useful:
| Web concept | Agent equivalent |
|---|---|
| Session cookie for your app | A harness session for a long-running agent thread |
| JWT access token across services | A scoped token a tool server verifies without calling back to the issuer |
| OAuth delegated authorization | An agent acting on a user's behalf with limited scope — the MCP authorization spec is built on OAuth |
| OIDC identity | Who the human is, before any tool is allowed to act for them |
And the same mistakes reappear, with higher stakes: treating an access token as identity, storing long-lived tokens where a prompt injection can read them, and granting scopes wider than the task. An agent that holds a broad token is a bearer token with initiative.
✅ Claims checked
| Claim | Verdict | Evidence |
|---|---|---|
| Sessions, JWTs and OAuth solve different parts of the problem | Correct | OAuth 2.0 (RFC 6749) is an authorization framework; JWT (RFC 7519) is a token format; sessions are server-side state |
| "Sign in with Google" is OIDC layered on OAuth 2.0 | Correct | OpenID Connect Core 1.0 defines the identity layer and the ID token |
| Session data lives on the server; the browser holds an identifier | Correct | Standard session model with a session ID cookie |
Recommended cookie attributes: HttpOnly, Secure, appropriate SameSite | Correct | Standard practice; he says "HTTP-only" — the attribute is HttpOnly |
| A signed JWT is not encrypted; anyone holding it can read header and payload | Correct and important | JWS vs JWE in RFC 7519; Base64URL is encoding |
| JWT validators should check signature, issuer, audience, expiry, scopes | Correct | Registered claims in RFC 7519; per-service audience checks are standard practice |
| Immediate revocation of a self-contained JWT needs extra server-side state | Correct | Denylist or token-version approaches reintroduce a lookup |
| Refresh tokens need rotation and reuse detection | Correct | Refresh token rotation with reuse detection is standard guidance (and part of RFC 9700's recommendations) |
localStorage exposes tokens to any page JavaScript; cookies change the threat model to CSRF | Correct | The XSS/CSRF distinction is exactly as described |
| PKCE protects authorization codes for public clients | Correct | RFC 7636; RFC 9700 strengthens this to all clients |
| OAuth access tokens are not proof of identity; use OIDC | Correct | Core reason the ID token exists |
| Don't roll your own cryptographic protocol | Correct | Consistent with every security BCP |
Algorithm confusion and alg: none | Missing from the video | Well-documented JWT attack class; pin accepted algorithms and never trust the header |
| Key rotation / JWKS | Missing | Required to implement "validate the signature" in practice, including kid selection |
| RFC 9700 requirements (PKCE for all clients, no implicit flow) | Missing | OAuth 2.0 Security Best Current Practice |
| BFF pattern for single-page apps | Missing | The current recommended answer to browser token storage |
| Passkeys / WebAuthn as the modern authentication step | Missing | Not mentioned at any point in 23 minutes |
| MCP authorization is built on OAuth 2.0 | Verified | MCP specification, authorization section |
Transcript notes. Auto-captions are unusually clean here. Corrections: "HTTP-only" → HttpOnly, "a day list" → denylist, "authorization equals bearer current access token" → Authorization: Bearer <access token>, "Calender" → calendar.
🔑 Key takeaways
- They are not competitors. Sessions and JWTs represent authenticated state; OAuth delegates authorization; OIDC authenticates users through an identity provider.
- "Login with Google" is OIDC, not OAuth. An access token is for resources; an ID token is for identity — conflating them causes real bugs.
- Default to server-side sessions for server-rendered apps. Easy to reason about, instantly revocable, and the shared-store objection is a solved infrastructure problem.
- A signed JWT is readable. Never put secrets in the payload; signing gives integrity, not confidentiality.
- Revocation is JWT's bill. Short lifetimes, refresh rotation and reuse detection are the payment; a denylist means you gave up statelessness and kept the complexity.
- Token storage swaps threats:
localStoragebuys XSS exposure, cookies buy CSRF — unless you use a BFF and keep tokens server-side. - Validate more than the signature: issuer, audience, expiry and scopes, plus a pinned algorithm list and a real key-rotation story.
- PKCE is not optional for public clients, and RFC 9700 now requires it for all clients while prohibiting the implicit flow.
- Compose them: OIDC to sign in, your own session for the browser, OAuth or scoped tokens for calls to other services.
- The best architecture is the least fashionable one that fits: solve your actual problem with the least unnecessary complexity.
- Two gaps to patch before shipping: algorithm confusion and key rotation. Both are how a "correct" JWT implementation still gets compromised.
- Agents inherit the same primitives: MCP authorization is OAuth, which is why scope minimization and short token lifetimes are the containment boundary for anything an agent can be talked into.
🔗 Sources
- The source video — Time To Program, "Stop Choosing JWT by Default! Sessions vs JWT vs OAuth Explained", 23:00, 12 October 2026
- RFC 7519 — JSON Web Token (JWT)
- RFC 6749 — The OAuth 2.0 Authorization Framework
- RFC 7636 — PKCE
- RFC 9700 — Best Current Practice for OAuth 2.0 Security
- RFC 9449 — OAuth 2.0 Demonstrating Proof of Possession (DPoP)
- OpenID Connect Core 1.0
- MCP specification — Authorization