Skip to main content

Command Palette

Search for a command to run...

Sessions vs Token-Based Authentication

A practical look at how stateful sessions, stateless tokens, and hybrid models work—and how they shape real-world authentication.

Updated
8 min read
Sessions vs Token-Based Authentication

Series: Web Authentication Demystified — From Concepts to Real-World
ARC 1 — FOUNDATIONS OF USER AUTHENTICATION
Previous: Authentication Flows: Choosing the Right One
Next: Architecture Overview of my implemented Web App Authentication System

Modern applications authenticate users in two fundamentally different ways: by relying on stateful sessions managed by the server, or by using stateless tokens such as JWTs. Both approaches are widely used today, and both come with trade-offs that become especially important as systems scale across browsers, devices, APIs, and microservices.

This article connects theory to practice by breaking down how these models work, where they fit, and how real-world systems (including my real world implementation) often use a hybrid of both.


Stateful Sessions: Server-Managed Authentication

Traditional web apps—PHP, Rails, Django, ASP.NET—authenticate users using a server-managed session. The key idea is simple:

The server controls the truth about who the user is.

Once the user logs in, the server creates a session entry in a session store (like Redis or a database), and gives the browser a cookie with a session ID. On every request:

  1. The browser sends the cookie

  2. The server looks up the session ID

  3. The server restores the user’s identity

Example (PHP Session + Redis)

  • The session data (user ID, roles, timestamps) lives in Redis

  • The browser receives a PHPSESSID cookie

  • Every request includes this cookie, and the backend rehydrates the user session

Pros

  • Very secure: tokens never exposed to JavaScript

  • Easy logout: deleting the server session immediately invalidates the user

  • Centralized control: backend can revoke sessions anytime

  • Mature ecosystem: all server frameworks support it

Cons

  • Requires shared session storage (Redis) to scale horizontally

  • APIs or microservices can’t validate sessions on their own

  • Session data grows as users increase

Stateful sessions remain the best choice for server-rendered websites, enterprise admin dashboards, and apps that require precise session control.


Stateless Authentication: JWT-Based Identity

JWTs (JSON Web Tokens) allow the server to authenticate requests without storing anything. Instead of a session ID, the server receives a full, signed token containing identity and authorization details.

A JWT is self-contained:

  • Identity (sub)

  • Expiration (exp)

  • Audience (aud)

  • Issuer (iss)

  • Optional roles, permissions, or claims

The server verifies:

  • the signature (using JWKS),

  • timestamps,

  • audience,

  • issuer

…and if everything checks out, the request is authenticated.

Pros

  • Stateless: no database lookup needed

  • Efficient for microservices: each service validates tokens locally

  • Easy horizontal scaling

  • Great for mobile, SPAs, and APIs

Cons

  • Revocation is hard: once a JWT is issued, it’s valid until it expires

  • Larger token size increases bandwidth

  • Security risk if stored incorrectly (e.g., localStorage XSS leak)

  • Complexity of key rotation / token versioning

JWTs shine in distributed systems where performance and scalability matter more than central revocation.


Hybrid Strategies: Best of Both Worlds

Many real systems—including my implemented HumHub–IdP integration—use a hybrid approach:

Process Overview

  1. User logs in through an external IdP

  2. The IdP issues an access token (JWT or opaque)

  3. The backend validates the token

  4. Instead of using the JWT as the session, the backend creates a server-side session (PHPSESSID, Redis-backed)

  5. The browser only holds the session cookie, not the access token

This combines the strengths of both models.

Why this works so well

  • IdP handles identity and security

  • Backend session handles app logic and permissions

  • Tokens never touch the browser, reducing risk

  • API calls from backend → IdP use access tokens safely

  • Logout is instantaneous, since session deletion kills the login

This hybrid model is extremely common in enterprise systems integrating with OAuth2/OIDC IdPs.

Real World Example (based on my experience)

Use Case: A public-sector teacher using their government identity to access a 3rd-party education web application (built on Yii / HumHub).

Imagine a math teacher, Ms. Linh, who works for a public-sector education institute. Her government issues a centralized identity account that she can use across approved third-party platforms—including my implemented education web app (CSL).

1. Ms. Linh clicks “Login with Government Identity” in the CSL app

The CSL application immediately redirects her to the government IdP’s /authorize endpoint.

From her perspective, she’s just visiting a familiar login page.
From the system's perspective, the CSL app is saying:

"Please authenticate this user and tell us who they are."

2. The government IdP authenticates her

She enters her username, password, and a second-factor OTP.

The IdP now knows:

  • This is really Ms. Linh

  • Her government identity is valid

  • She belongs to a recognized education institute

The IdP asks whether she wants to allow CSL to access basic profile information—name, email, unique teacher ID.

She agrees.

3. The IdP issues an access token (JWT or opaque)

After successful authentication, the IdP redirects CSL back with an authorization code.

CSL’s backend exchanges the code at the IdP’s /token endpoint and receives:

  • access_token — proving that CSL has permission to call the government APIs

  • id_token (optional) — containing standard OIDC claims

  • token_type — usually "Bearer"

  • expires_in — token lifetime

Crucially:
These tokens exist only in CSL’s backend, never in the browser.

4. The backend validates the access token

CSL now verifies the token:

  • Signature checked through the IdP’s /jwks endpoint

  • Issuer (iss) matches the government IdP

  • Audience (aud) matches CSL’s client ID

  • Expiration (exp) is valid

  • Claims (sub, email, etc.) provide a trustworthy identity

Once validated, the backend extracts the teacher’s government sub (unique ID) and normalizes it to CSL’s internal user_id.

At this point, authentication is complete and identity is trusted.

But here’s where hybrid strategy shines.

5. Instead of using the JWT in the browser, CSL creates a server-side session

CSL doesn’t hand the JWT to the browser.

It does something safer:

  1. It stores Ms. Linh’s CSL user record in Redis:

     {
       "user_id": 4821,
       "gov_sub": "teacher-98765",
       "name": "Linh Nguyen",
       "email": "linh.nguyen@example.gov",
       "roles": ["teacher"],
       "login_time": 1733639200
     }
    
  2. It generates a PHPSESSID session ID and sends it to the browser in a HttpOnly, Secure, SameSite cookie.

This means:

  • The browser never sees the real access token

  • JavaScript cannot read or steal the cookie

  • Backend remains the single source of truth

Every request the browser makes simply includes:

Cookie: PHPSESSID=abc123xyz

The backend loads the corresponding Redis session and confirms the user is authenticated.

6. The browser only holds the session cookie, not the access token

Ms. Linh’s session is now:

  • Identified by a cookie

  • Protected from XSS

  • Fully revocable by deleting the Redis session

  • Consistent with how HumHub manages user sessions internally

If she logs out, CSL simply deletes the session entry in Redis.
In one instant, the session is dead—something JWT-only systems struggle with.


Cookie vs localStorage vs Memory: Where Should Tokens Live?

A crucial design decision in any auth system is where to store the token or session identifier in the browser.

1. HttpOnly Cookies (Best for Security)

  • Sent automatically with requests

  • Not accessible to JavaScript

  • Mitigates XSS token theft

  • Supports SameSite rules

Used in stateful sessions and secure hybrid approaches.

2. localStorage (Convenient but Risky)

  • Easy to access from JavaScript

  • Highly vulnerable to XSS

  • Tokens can persist too long

An attacker who injects JS gets your token—game over.

Never store refresh tokens in localStorage.

3. In-memory Storage (SPAs / Mobile)

  • Token lives in JS runtime memory only

  • Lost on page refresh (can be acceptable)

  • Greatly reduces XSS persistence risk

Often combined with:

  • Authorization Code + PKCE

  • Silent refresh

  • Session cookies for refresh tokens


Logout, Revocation, and Token Rotation

Logout

  • Stateful session: destroy the session → user is logged out instantly

  • JWT: cannot be “destroyed” → must rely on short expiration times

Revocation

  • Stateful systems: easy

  • JWT systems: hard; requires

    • token blacklists

    • token version fields

    • rotating refresh tokens

Token Rotation

Modern systems use:

  • Short-lived access tokens (5–15 minutes)

  • Long-lived refresh tokens (rotated on each use)

  • Revocation lists when refresh tokens are stolen

This balances user experience with security.


Security Implications of Each Approach

Stateful Sessions

  • Hardened by decades of use

  • Minimal token exposure

  • Excellent control & revocation

  • Vulnerable to CSRF if cookies are not configured properly (SameSite, Secure, HttpOnly)

JWT-Based Authentication

  • Strong cryptographic verification

  • Great for horizontal scaling

  • More vulnerable if tokens leak

  • Requires careful expiration & rotation design

Hybrid

  • Most secure for web apps using external IdPs

  • Reduces attack surfaces dramatically

  • Keeps backend logic consistent

  • Avoids browser token storage pitfalls


Which Approach Should You Use? (Quick Guide)

Classic Web Apps (PHP, Rails, Laravel, Django)

Stateful sessions + OIDC Login via Authorization Code Flow

SPAs (React, Vue, Angular)

Authorization Code + PKCE
Store access token in memory, use refresh token in HttpOnly cookie.

Mobile Apps

Authorization Code + PKCE
Store tokens in secure storage.

Microservices

JWT-based stateless authentication
Validated locally per service.

Enterprise SSO Integrations

Hybrid model
(IdP → backend tokens → server session)


Final Thoughts

Sessions and tokens are not competing ideas—they’re tools suited to different environments.
Understanding their trade-offs is essential for building secure, scalable authentication systems.

  • Sessions excel in security & simplicity.

  • JWTs excel in scalability & distributed systems.

  • Hybrid systems give you the best practical balance.

In the next article, we’ll move from concepts to architecture and explore my real-world authentication implementation in CSL—how the IdP, HumHub, Redis sessions, and CloudFront all fit together into a cohesive identity system.

Web Authentication Demystified — From Concepts to Real-World

Part 9 of 14

This series breaks down modern web authentication, from core concepts like OAuth, OIDC, and tokens to real implementation details. You’ll learn how the pieces fit together and see how a production-ready system is built and secured.

Up next

Authentication Flows: Choosing the Right One

A practical guide to understanding OAuth2 and OIDC flows—and how to choose the right one for your app.