Skip to main content
the auth layer

How Authentication Actually Works: Tokens, Sessions, and the State You Are Hiding

7 min read Chapter 1 of 45

How Authentication Actually Works

Every authentication system hides state. The question is where.

A session cookie hides state on the server. A JWT hides state on the client. A refresh token hides state in a database. The authentication flow your application uses is a decision about where to put that state, who can invalidate it, and how fast invalidation propagates. Most teams make this decision by following a tutorial. The tutorial works. Then a security auditor asks how you revoke access for a compromised account within 30 seconds, and the answer is: you cannot.

This chapter establishes the foundational tension between stateless and stateful authentication, introduces the multi-tenant SaaS platform that runs through every chapter of this book, and states the four opinions that drive every design decision that follows.

The Token-Session Spectrum

Authentication is not a binary choice between “sessions” and “tokens.” It is a spectrum with pure server-side state at one end and pure client-side state at the other. Every real system sits somewhere in the middle.

Pure server-side state: The server generates a random session ID, stores all session data in a server-side store, and sends only the opaque ID to the client in a cookie. The client knows nothing about its own identity. The server knows everything. Revocation is instant: delete the session from the store.

Pure client-side state: The server generates a signed token containing all identity and authorization data, sends it to the client, and stores nothing. The client carries its own identity proof. The server is stateless. Revocation is impossible without reintroducing state.

Every production system lives between these extremes. The decisions you make about where on this spectrum each boundary of your system sits determine your revocation latency, your scalability characteristics, your attack surface, and your operational complexity.

Why JWTs Are Overused

JWTs are a brilliant solution to a specific problem: validating identity claims across trust boundaries without requiring the validating party to contact the issuing party. A resource server in a different network segment, operated by a different team, can verify a user’s identity by checking a cryptographic signature against a public key. No database call. No network call to the authorization server. Pure math.

This property is valuable when the resource server cannot or should not contact the authorization server on every request. Service-to-service communication across organizational boundaries. API gateways validating tokens for downstream services. Third-party integrations consuming your API.

This property is irrelevant when the resource server is your own application, sitting next to your own session store, with sub-millisecond access to Redis. In that case, a JWT gives you nothing except a token that cannot be revoked until it expires.

The JWT tutorial ecosystem has conflated “stateless” with “modern” and “sessions” with “legacy.” The result is browser-facing applications issuing 15-minute JWTs with refresh tokens, implementing token rotation, building revocation lists, and solving problems that sessions solved decades ago. The total system complexity exceeds what a session-based approach requires, and the security properties are strictly worse for the browser-facing use case.

This book’s position: use JWTs for service-to-service validation across trust boundaries. Use server-side sessions (backed by Redis, not by the servlet container’s in-memory store) for browser-facing applications where you need instant revocation.

The Multi-Tenant SaaS Platform

Every chapter in this book uses the same domain: a multi-tenant SaaS platform with five components that surface every auth problem naturally.

Core API (Spring Boot 3, Java 21): The central business logic service. Handles tenant-scoped data, enforces authorization, and validates tokens. This is the resource server for external requests and the token exchange initiator for internal calls.

Frontend Shell (Single-Page Application): The browser-facing client. Authenticates users via authorization code flow with PKCE. Stores tokens in memory (not localStorage). Needs instant session invalidation because users share devices and users get terminated.

Mobile Client (Native app): Authenticates via authorization code flow with PKCE. Stores refresh tokens in platform-secure storage (Keychain, Keystore). Token lifetimes are longer because the device is trusted. Revocation is critical because devices get stolen.

Third-Party Integrations (External OAuth2 clients): Partners who access tenant data via OAuth2 client credentials or authorization code grants. Scoped by tenant. Scoped by permission. Subject to rate limiting. Their tokens must not grant access beyond what the tenant administrator approved.

Internal Microservices (Spring Boot, service mesh): Payment processing, notification service, analytics pipeline. Communicate via mTLS with SPIFFE identity. Propagate user context via token exchange when acting on behalf of a user. Never store user credentials. Never accept user tokens directly.

Each component sits at a different point on the token-session spectrum. Each has a different threat model. Each requires a different Spring Security configuration. The book builds all five incrementally.

The Four Opinions

Spring Authorization Server is the right foundation for a custom OAuth2/OIDC server on the JVM. It implements RFC 6749, RFC 7636, RFC 7662, RFC 7009, and OpenID Connect Core correctly. It integrates with Spring Security’s filter chain natively. It gives you programmatic control over every token claim, every signing key, every storage backend. You are not fighting the framework to implement spec-compliant behavior. Keycloak provides most of this out of the box with an admin UI, user federation, and theme support. It is the right choice when you need a working system in weeks and your customization needs are moderate. It is the wrong choice when you need custom token logic that the SPI system does not support cleanly, or when you cannot afford the JVM memory overhead of a separate deployment. Chapter 15 gives the full decision framework.

JWTs are overused. Chapter 6 provides the decision rule. If your resource server can contact your authorization server with acceptable latency, use opaque tokens with introspection. If your resource server cannot contact your authorization server (cross-boundary, third-party, high-scale fan-out), use JWTs. Do not use JWTs because they are “modern.”

The auth layer is an attack surface first and a feature second. Every design decision in this book is evaluated against a concrete attack. A token format choice that does not consider replay is incomplete. A session configuration that does not consider fixation is incomplete. A password storage choice that does not consider GPU hash rates is incomplete. Functionality that is not evaluated against its attack surface is functionality waiting to be exploited.

Keycloak is a legitimate managed alternative, not a competitor to understanding. Teams that deploy Keycloak still need to understand token lifecycle, key rotation, and attack surfaces. Keycloak handles the protocol implementation. It does not handle your application’s authorization logic, your token validation configuration, or your incident response procedures. Understanding what happens inside the auth server matters regardless of who wrote the code.

What This Book Is Not

Not an OAuth2 tutorial for beginners. If you need to learn what an authorization code is, read the RFC first, then come back.

Not a Spring Security getting-started guide. If you have never configured a SecurityFilterChain, the Spring Security reference documentation is your first stop.

Not a compliance checklist. SOC2, GDPR, PCI-DSS all have auth-relevant requirements, but this book does not map controls to compliance frameworks.

Not a frontend authentication guide. Browser token storage, CORS configuration, and SPA redirect handling are mentioned where they affect the backend auth design, but frontend implementation is not the focus.

The reader already has an auth system. This book explains where it is vulnerable, where it will fail under load, and how to make it production-grade using Spring Security and Spring Authorization Server as the implementation foundation.