JWT Authentication: Stateless Security
Sessions vs JWTS. The debate is endless. When to use JSON Web Tokens, how to store them securely (HttpOnly), and how to handle rotation.
The Session vs Token Debate
Session Auth (The Classic):
- User logs in.
- Server creates a Session ID (
sess_123). Stores it in Memory/Redis. - Server sends ID to Client cookie.
- Client sends cookie.
- Server looks up
sess_123in Redis. Finds “User is Bob”. Pros: Revocable (Delete from Redis = User logged out). Cons: Statefull. You need Redis. Harder to scale across regions.
JWT Auth (The Modern):
- User logs in.
- Server creates a JSON Web Token. Contains
{ id: 1, name: "Bob" }. Signs it with a Secret Key. - Server sends JWT to Client.
- Client sends JWT.
- Server verifies signature. Finds “User is Bob”. Pros: Stateless. No database lookup required. “The token is the session”. Cons: Irrevocable. If stolen, valid until expiry.
Why Maison Code Discusses This
At Maison Code, we build Microservices and Mobile Apps. Sessions are terrible for Mobile Apps (Cookies are hard). Sessions are terrible for Microservices (Sharing a Redis instance across 10 services is coupling). JWT is the universal passport. Service A can verify the token issued by Auth Service just by knowing the Public Key. We implement strict JWT flows to enable our Headless Architectures.
The Anatomy of a JWT
Three parts, separated by dots.
Header.Payload.Signature.
- Header: Algorithm (
HS256). - Payload: Data (
sub: 123,exp: 17000000). - Signature:
Hash(Header + Payload + Secret).
Critical: The Payload is Base64 Encoded, NOT Encrypted.
Anyone can read it (atob(token)).
Rule: Never put secrets in the JWT. No passwords. No credit cards.
Only Public User IDs and Roles.
Where to Store it? (The Holy War)
Option 1: LocalStorage
- Easy (
localStorage.setItem('token', t)). - Vulnerable to XSS. If attacker runs JS, they read the token.
Option 2: HttpOnly Cookie
- Secure. JS limits access.
- Vulnerable to CSRF.
- Requires
SameSite=Strict.
Maison Code Verdict: HttpOnly Cookie. CSRF is easier to mitigate (SameSite) than XSS. For Mobile Apps, use Secure Storage (Keychain).
The Access Token / Refresh Token Pattern
To solve the “Irrevocable” problem.
- Access Token: Short life (5 minutes). Used to call API.
- Refresh Token: Long life (7 days). Stored securely (HttpOnly/DB). Used only to get a new Access Token.
Flow:
- Client makes API call with Access Token.
- Server says “401 Expired”.
- Client calls
/refresh-tokenwith Refresh Token. - Server checks DB. “Is this Refresh Token blocked? No.”
- Server issues new Access Token. Security: If Access Token is stolen, it works for 5 mins. If you ban the user, you delete the Refresh Token from DB. They are locked out in max 5 mins.
Algorithm: HS256 vs RS256
- HS256 (Symmetric): Server signs and verifies with same Secret. Fast. Simple for Monoliths.
- RS256 (Asymmetric): Auth Service signs with Private Key. Other Services verify with Public Key.
- This is crucial for Microservices. The “Product Service” can verify the user without knowing the Auth Service’s secret.
The Skeptic’s View
“Sessions are simpler.” Counter-Point: Yes, for a monolith with one server. Try doing sessions when you have a Next.js frontend on Vercel Edge Network and a Go backend on AWS. The latency of “Checking Redis” kills Edge performance. JWT allows checking auth at the Edge (Verify signature in 1ms) without a DB trip.
FAQ
Q: Can I manually invalidate a JWT? A: No. That’s the point. You can implement a “Blacklist” (store invalid JWT IDs in Redis), but then you re-invented Sessions. Use Short Expiry times instead.
Q: How huge can a JWT be? A: Keep it small. It is sent in every HTTP Header. If you put 50 permissions in it, you slow down every request.
10. Token Rotation (The Refresh Chain)
Standard Refresh Tokens have a flaw: If stolen, the thief has access for 7 days. Solution: Refresh Token Rotation.
- Client sends Refresh Token A.
- Server returns Access Token B AND New Refresh Token C.
- Server deletes Refresh Token A. If the thief tries to use “Old Refresh Token A” again -> Security Alarm. The server detects reuse of a revoked token. It immediately invalidates the entire “Token Family” (A, C, and all descendants). The thief is locked out instantly. The real user is asked to re-login.
11. JWKS (JSON Web Key Sets)
How do you rotate the Signing Keys?
You can’t rebundle the Public Key into the apps every week.
Solution: JWKS Endpoint (/.well-known/jwks.json).
The Auth Server publishes its Public Keys at a URL.
The Resource Server fetches them dynamically (and caches them).
If you suspect a key compromise, you rotate the key on the Auth Server, update the JWKS, and everyone picks up the new key in 5 minutes.
12. OAuth 2.1 and The Future
JWT is just the token format. OAuth 2.0 is the flow. The industry is moving to OAuth 2.1 (Consolidating best practices).
- No more Implicit Flow (access token in URL hash).
- PKCE (Proof Key for Code Exchange) is mandatory. We implement PKCE even for Server-Side web apps now. It prevents “Authorization Code Injection” attacks.
13. Machine-to-Machine Auth (Client Credentials)
What if “Cron Job A” needs to call “API B”?
There is no user. No password.
We use Client Credentials Flow.
POST /token { client_id, client_secret }.
Returns a JWT.
This standardizes Auth. Whether it’s a User (sub: user_1) or a Machine (sub: service_billing), the API just validates the JWT signature.
Uniformity is security.
14. Conclusion
JWT is the currency of the modern web.
Like Money, it must be minted carefully (Signing), stored securely (Safe), and checked for counterfeits (Verification).
Do not roll your own crypto. Use libraries (jsonwebtoken, jose).
Broken Auth?
If users are getting logged out randomly, or you are storing tokens in LocalStorage, Maison Code can secure your Auth flow.
Auth Headaches?
We secure Stateless Authentication using JWTs (Access/Refresh pattern) and HttpOnly cookies to protect identities across microservices. Hire our Architects.