Deep Dive · Web security

Sessions vs JWT vs OAuth: Choosing an Auth Architecture on Purpose

The three are not competing answers to one question. A careful walkthrough of what each actually solves — plus what the video leaves out: algorithm confusion, key rotation, the OAuth security BCP, passkeys, and what all of this means when the client is an agent.

Stop Choosing JWT by Default! Sessions vs JWT vs OAuth Explained — video thumbnail
Video: Time To Program 23:00 12 October 2026
Sessions JWT OAuth 2.0 · OIDC RFC 9700

🎯 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:

"Sessions, JWTs, and OAuth aren't three competing solutions to exactly the same problem. They solve different parts of the authentication and authorization puzzle."

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.

Disclosure. The description sells a paid "Complete Web Developer Bundle" (React components, projects, cheat sheets, interview Q&As) via a Buy Me a Coffee page. That link is not reproduced here; the technical content is independent of it.

🔤 Authentication versus authorization 1:25

ConceptQuestionExample
AuthenticationWho are you?Email and password, verified at login
AuthorizationWhat 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.
That last line is the correction most tutorials never make. "Login with Google" is OIDC, not OAuth. OAuth was designed to delegate access to resources, and treating an access token as proof of identity is the mistake that follows from blurring them — he comes back to it as mistake number three.

🍪 Sessions: server state, cookie identifier 2:40

The flow in four steps, and the emphasis is on where the data lives:

StepWhat happens
1The user submits credentials
2The server verifies them and creates a session — stored in a database, Redis, or another session store
3The server sends a session ID to the browser in a cookie
4The 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.

The cookie flags he names are the ones that matter: 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.

Reframed honestly, the trade is: sessions put a lookup on every request and give you instant revocation; JWTs remove the lookup and give you delayed revocation plus key management. Neither is free — they just bill you at different layers.

🧾 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).

The sentence that saves projects: "A typical signed JWT is not encrypted. Anyone who obtains it can generally decode its header and payload." So never put passwords, secret API keys or sensitive personal data in an ordinary signed JWT. Base64URL is encoding, not protection.

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:

CheckWhat it prevents
SignatureForged 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 / permissionsAuthenticated 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.

MitigationEffectCost
Short-lived access tokensNarrows the window a stolen token worksMore refreshes, more round trips
Refresh tokens with rotationLong sessions without long-lived access tokensRotation logic, secure storage
Reuse detectionDetects a stolen refresh token being replayedServer-side state per token family
Denylist / token versionImmediate revocation for specific tokens or usersReintroduces the per-request lookup you were avoiding
His sharpest point is the last row: if you need immediate revocation, you end up keeping server-side state — "at that point, some of the statelessness advantages may disappear". That is the honest accounting. Choose JWTs because you need portable, verifiable tokens across services; if you choose them for statelessness and then add a denylist, you kept the complexity and lost the benefit.

🗄 Where to store the token, and which threat you bought 9:24

The trade-off, stated cleanly:

StorageExposureNew risk introduced
localStorageAny JavaScript on the page can read it — one XSS is full token theft—
CookieNot readable by JS if HttpOnlySent automatically, so CSRF becomes relevant again
OS secure storage (mobile)Isolated from app codePlatform-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.

ConceptPurpose
OAuth 2.0 access tokenAccess to protected resources — not proof of identity
OIDC ID tokenAuthentication claims about the user, for the client it was issued to
Authorization code flowThe 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.

The sentence to highlight for anyone building "Sign in with Google": use OpenID Connect, not bare OAuth. An OAuth access token is for calling APIs; an ID token is what tells your app who just logged in, and only the latter is meant for that job.

🆔 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

SessionsJWTOAuth / OIDC
State livesServer, browser holds an IDClient holds a signed tokenAuthorization server issues tokens; types vary
Logout / revocationImmediateNeeds extra mechanismDepends on token type and provider
ScalingShared storeMany services verify the same issuerDesigned for cross-service delegation
SolvesLogin state for one appPortable verified claimsDelegated access; identity via OIDC
Wrong tool whenYou need cross-service portable claimsYou need instant revocation without stateYou want "login" from bare OAuth

🧭 Four scenarios, four defaults 16:03

ScenarioDefaultNon-negotiables
Traditional server-rendered web appServer-side sessions + secure cookiesHTTPS, HttpOnly/Secure/SameSite, regenerate session ID on login and privilege change, CSRF defence, invalidate on logout
Mobile app calling an APIShort-lived access token + refresh tokenOS secure storage; no client secrets in the app; opaque tokens are a valid choice when introspection is needed
Multiple backend servicesJWT access token from a trusted issuerValidate signature, iss, aud, exp, scopes; do not forward the same token to every service; each service enforces its own authorization
Sign in with GoogleOIDC for login, then your own session or token for your appValidate 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

  1. Assuming JWTs are more secure than sessions. Security lives in implementation, storage, expiry, key management and revocation.
  2. Assuming JWT payloads are secret. A signed token is readable by whoever holds it.
  3. Using OAuth access tokens as login proof. Use OIDC for authentication through an identity provider.
  4. Forgetting token theft and logout. Deleting a client-side copy does not invalidate a stolen one — design expiry, revocation, rotation and session invalidation deliberately.
  5. 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:

GapWhy it matters
Algorithm confusion / alg: noneA 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 JWKSSigning 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 BCPThe 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 patternFor 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 / WebAuthnConspicuously 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 SSOSAML 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.
The one terminology slip worth fixing: the cookie attribute is 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 conceptAgent equivalent
Session cookie for your appA harness session for a long-running agent thread
JWT access token across servicesA scoped token a tool server verifies without calling back to the issuer
OAuth delegated authorizationAn agent acting on a user's behalf with limited scope — the MCP authorization spec is built on OAuth
OIDC identityWho 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.

The practical takeaway for anyone building agents: the reason MCP chose OAuth rather than inventing something new is the property the video spends ten minutes on — delegated, revocable, scoped access without handing over the credential. Scope minimization and short token lifetimes are not compliance chores; they are the containment boundary for a component that can be talked into things.

✅ Claims checked

ClaimVerdictEvidence
Sessions, JWTs and OAuth solve different parts of the problemCorrectOAuth 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.0CorrectOpenID Connect Core 1.0 defines the identity layer and the ID token
Session data lives on the server; the browser holds an identifierCorrectStandard session model with a session ID cookie
Recommended cookie attributes: HttpOnly, Secure, appropriate SameSiteCorrectStandard practice; he says "HTTP-only" — the attribute is HttpOnly
A signed JWT is not encrypted; anyone holding it can read header and payloadCorrect and importantJWS vs JWE in RFC 7519; Base64URL is encoding
JWT validators should check signature, issuer, audience, expiry, scopesCorrectRegistered claims in RFC 7519; per-service audience checks are standard practice
Immediate revocation of a self-contained JWT needs extra server-side stateCorrectDenylist or token-version approaches reintroduce a lookup
Refresh tokens need rotation and reuse detectionCorrectRefresh 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 CSRFCorrectThe XSS/CSRF distinction is exactly as described
PKCE protects authorization codes for public clientsCorrectRFC 7636; RFC 9700 strengthens this to all clients
OAuth access tokens are not proof of identity; use OIDCCorrectCore reason the ID token exists
Don't roll your own cryptographic protocolCorrectConsistent with every security BCP
Algorithm confusion and alg: noneMissing from the videoWell-documented JWT attack class; pin accepted algorithms and never trust the header
Key rotation / JWKSMissingRequired to implement "validate the signature" in practice, including kid selection
RFC 9700 requirements (PKCE for all clients, no implicit flow)MissingOAuth 2.0 Security Best Current Practice
BFF pattern for single-page appsMissingThe current recommended answer to browser token storage
Passkeys / WebAuthn as the modern authentication stepMissingNot mentioned at any point in 23 minutes
MCP authorization is built on OAuth 2.0VerifiedMCP 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: localStorage buys 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

Basis note. Built from the video's 23:00 transcript. Every protocol claim was checked against the RFCs and the OIDC and MCP specifications; the "what the video leaves out" section is editorial addition and is labelled as such rather than attributed to the speaker. The video's paid bundle link is excluded.

⏱ Timestamp index

0:00The architectural decision, and the false debate
1:25Authentication vs authorization
2:03Where each technology fits; OIDC named
2:40Sessions begin: the four-step flow
3:52Why sessions are useful
4:19The shared session store objection
5:04Cookie flags: HttpOnly, Secure, SameSite
5:42JWT anatomy: header, payload, signature
6:33Signed is not encrypted
6:51The bearer-token flow
7:24What the API validates
8:13JWTs are not automatically faster
8:43The revocation problem
9:24localStorage vs cookie vs secure storage
10:08OAuth: the calendar example
11:15Where Google login fits: OIDC
12:08Authorization code flow and PKCE
12:57Side-by-side comparison
14:31The most important takeaway
16:03Scenario 1: traditional web app
17:08Scenario 2: mobile app calling an API
18:04Scenario 3: multiple backend services
19:10Scenario 4: sign in with Google
19:55Five common mistakes
21:26Don't implement your own crypto
21:39Final decision guide
22:48Closing question to the audience
☰ View all