Stateless Auth for REST APIs
Stateless Auth for REST APIs
Before you write a single line of JWT code you need to understand why the industry moved away from server-side sessions for REST APIs. Without that context, JWT just looks like a complicated cookie — and you will make the wrong design choices when it matters.
How Classic Session-Based Auth Works
In a traditional monolithic web application, authentication works like this:
- The user submits credentials (username + password).
- The server validates them, creates a session object in memory (or in a database), and stores the user's identity and roles there.
- The server sends back a
Set-Cookie: JSESSIONID=abc123header. - Every subsequent request from the browser carries that cookie automatically.
- On each request the server looks up
abc123in its session store to find out who is making the request.
This works beautifully for a single server with a browser client. The problem is the phrase "looks up in its session store" — that lookup is the root of all the pain you will see in distributed systems.
Why Sessions Do Not Fit REST APIs
1. REST Is Defined as Stateless
Roy Fielding's original REST dissertation makes statelessness a hard architectural constraint: each request from a client must contain all the information necessary for the server to understand and process it. The server must not store any client context between requests. A session store violates this constraint directly — the server needs a side-channel lookup to understand the request.
This is not just philosophical. Statelessness is what makes REST services independently scalable, cacheable, and simple to reason about.
2. Horizontal Scaling Requires Shared State
Modern deployments run multiple instances of every service behind a load balancer. Consider a Spring Boot API deployed as three pods in Kubernetes:
A user authenticates against Pod A. Their session is in Pod A's in-memory store. The next request is routed to Pod B by the load balancer — Pod B knows nothing about that session and returns 401 Unauthorized. The user is suddenly logged out mid-workflow.
The standard fix — sticky sessions (pin each user to one pod) or a shared session store (Redis, a database) — patches the symptom but introduces new problems:
- Sticky sessions break when a pod restarts or the cluster auto-scales. One failed node logs out all users pinned to it.
- Shared session store turns the session store itself into a centralised bottleneck and single point of failure. Every authentication check requires a network round-trip to Redis or the database.
3. Microservices Span Trust Boundaries
In a microservices architecture an incoming request may fan out to an Order Service, an Inventory Service, and a Notification Service — each running as an independent process, possibly owned by a different team. Session cookies cannot propagate across service boundaries because:
- Cookies are scoped to a domain; service-to-service calls are HTTP, not browser navigation.
- There is no single session store all services can query without tight coupling.
- Each service would need to call an auth service on every request, multiplying latency and coupling.
A self-contained, cryptographically signed token that each service can verify independently is the only practical solution.
4. Non-Browser Clients Do Not Handle Cookies Well
REST APIs are consumed by mobile apps, CLI tools, IoT devices, and other backend services. None of these have a browser's automatic cookie management. Implementing session cookies in an Android app or a Python script is awkward and error-prone. An Authorization: Bearer <token> header is universally supported by every HTTP client in every language.
What Stateless Auth Looks Like
With stateless authentication the server never persists any session data. Instead:
- The client authenticates once and receives a signed token.
- The client sends that token in the
Authorizationheader of every subsequent request. - The server validates the token's signature and extracts the identity and roles directly from the token — no database lookup required.
The server's validation is purely computational — it verifies a cryptographic signature using a key it already holds in memory. No network I/O, no shared store, no sticky routing required.
The Trade-offs You Must Know
Stateless auth is not free. You are trading one set of problems for another. A working developer needs to be clear-eyed about both sides:
- Token revocation is hard. A session can be invalidated instantly by deleting it from the store. A stateless token is valid until it expires. If a user logs out or is compromised, the token still works until expiry. The standard mitigations — short-lived tokens (5–15 minutes) plus refresh tokens, or a token blocklist — are covered in later lessons.
- Token size grows with claims. A session ID is a tiny string; a JWT carrying roles, permissions, and user metadata is hundreds of bytes sent on every request. This matters at scale or on constrained networks.
- Secret key management becomes critical. Anyone who holds your signing key can forge tokens. Key rotation, secure storage (not in source code), and algorithm selection are now your responsibility.
Spring Security's Stateless Session Policy
By default, Spring Security creates an HttpSession and stores the SecurityContext in it. For a REST API you must explicitly disable this. The configuration below is the starting point for every stateless API you will build in this tutorial:
The key line is SessionCreationPolicy.STATELESS. With this setting Spring Security will never write a JSESSIONID cookie and will never look for one. The SecurityContext must be populated on every request from the token — which is exactly what the JWT filter you will build in Lesson 4 does.
Summary
Server-side sessions are incompatible with REST's statelessness constraint, break under horizontal scaling, cannot cross service boundaries, and are awkward for non-browser clients. Stateless tokens solve all four problems at the cost of harder revocation and more careful key management. The remaining lessons in this tutorial build the complete solution: JWT structure and validation, a reusable authentication filter, role-based access control, refresh tokens, and finally OAuth2 for delegated authorisation.