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.

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:
The browser sends the cookie
The server looks up the session ID
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
PHPSESSIDcookieEvery 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
User logs in through an external IdP
The IdP issues an access token (JWT or opaque)
The backend validates the token
Instead of using the JWT as the session, the backend creates a server-side session (
PHPSESSID, Redis-backed)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
/jwksendpointIssuer (
iss) matches the government IdPAudience (
aud) matches CSL’s client IDExpiration (
exp) is validClaims (
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:
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 }It generates a
PHPSESSIDsession 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.






