# JWT vs Session Authentication in Production: What I Actually Use

> An honest comparison of JWT and session-based authentication for production APIs — covering security trade-offs, revocation, storage, and what I implement for ERP systems.

URL: https://www.matthewswong.com/en/blog/jwt-vs-session-auth-production/

- Author: Matthews Wong (https://www.matthewswong.com/en/about/)
- Published: 2025-06-10
- Updated: 2026-10-17
- Category: DevOps

JWT vs session auth is one of those developer debates that generates more heat than light. I have implemented both in production — session auth for traditional server-rendered apps, JWT for API-first ERP systems — and the truth is that both can be implemented securely or insecurely depending on how you use them. The real question is not which is better in theory, but which fits your threat model, architecture, and team's ability to implement correctly. According to the 2024 Verizon DBIR, stolen credentials account for 24% of all data breaches — making authentication one of the highest-impact security areas to get right.

## JWT: The Good, The Bad, and The Dangerous

JWTs (JSON Web Tokens) are self-contained tokens that carry user claims (user_id, roles, permissions) and are signed by your server. The client sends the token on every request; the server verifies the signature without a database lookup. This stateless nature is the main selling point: horizontal scaling without shared session storage, no database round trip on every request, and easy cross-service authentication in microservices. The hidden costs: JWTs cannot be invalidated before expiry. If a token is stolen, it is valid until it expires. If a user's permissions change, old tokens reflect old permissions until they expire.

### Secure JWT Implementation

The non-negotiable JWT security requirements: use short expiry for access tokens (15 minutes is my standard), use a separate long-lived refresh token stored in an httpOnly Secure SameSite=Strict cookie (never localStorage), sign with RS256 (asymmetric) for service-to-service, HS256 (symmetric HMAC) for single-service with proper key management, validate all claims including exp, iat, iss, and aud on every request, and implement a token blacklist for the access token during the refresh window (a Redis set of revoked JTIs works well).

> From my experience building ERP authentication: implement a refresh token rotation strategy where each refresh generates a new refresh token and invalidates the previous one. Store the current valid refresh token hash in the database. This gives you revocation capability (delete the DB record to log out) without the full overhead of session storage. If you detect a refresh token being reused after rotation (the old token comes in after a new one was issued), it indicates token theft — immediately invalidate all refresh tokens for that user.

## Session Auth: Simpler and More Revocable

Traditional session auth stores a session ID in a cookie, and the server looks up session data on every request (typically from Redis). The advantages over JWT: immediate revocation (delete the session record), can store unlimited session data, simpler to implement correctly (fewer ways to make the algorithm=none mistake). The disadvantages: requires shared session storage for horizontal scaling (Redis solves this), slightly higher latency per request (Redis lookup adds 1-5ms), session fixation attacks require careful handling.

### The algorithm=none Vulnerability

The most dangerous JWT implementation mistake is accepting the alg=none JWT vulnerability. Early JWT libraries allowed the algorithm field in the token header to be set to 'none', meaning no signature was required. An attacker could forge a token with any claims by setting alg=none. Modern libraries have fixed this, but only if configured correctly: explicitly set the allowed algorithms and never accept alg=none. In Node.js with jsonwebtoken: always pass algorithms: ['HS256'] to the verify function. If you let the library read the algorithm from the token header, you may be vulnerable depending on the library version.

> **Production Warning**

> Storing JWT access tokens in localStorage is a common antipattern that opens your application to XSS attacks. Any JavaScript running on your page (including injected scripts from XSS) can read localStorage and steal tokens. Store access tokens in memory (JavaScript variable) and refresh tokens in httpOnly cookies. Yes, this means the access token is lost on page refresh — solve this by calling your refresh endpoint on app initialization to get a new access token using the httpOnly cookie. This pattern gives you both XSS resistance (httpOnly cookie) and CSRF resistance (short-lived access token in memory, not a cookie).

## What I Implement for ERP Systems

For API-first ERP systems, a hybrid approach works well: JWT access tokens (15-minute expiry, RS256 signed, stored in memory), refresh tokens (7-day expiry, stored as httpOnly Secure cookies), refresh token rotation with database-backed revocation (PostgreSQL table), and Redis blacklist for the current 15-minute window. This gives you the stateless verification benefits of JWT with revocation capability comparable to sessions. The database record for refresh tokens also enables 'log out everywhere' by deleting all records for a user ID.

## Multi-Device and Multi-Tenant Considerations

ERP systems often need multiple concurrent sessions (desktop + mobile) and multi-tenant isolation. For multiple devices: issue separate refresh tokens per device, store device fingerprint alongside the token, allow selective revocation of individual device sessions. For multi-tenancy: include tenant_id in the JWT payload, validate tenant_id matches the requested resource on every API call, use separate signing keys per tenant for high-security deployments. Implement refresh token family invalidation — if any token in a refresh chain shows signs of reuse, invalidate all tokens in that family.

## Checklist: Secure Auth Implementation

My implementation checklist for every auth system: access token expiry ≤ 15 minutes, refresh tokens in httpOnly Secure SameSite=Strict cookies, refresh token rotation enabled, explicit algorithm whitelist (no alg=none), token revocation mechanism (Redis blacklist or DB record), rate limiting on /login and /refresh endpoints, account lockout after N failures, MFA available (TOTP minimum), auth events logged with IP and user agent, session invalidation on password change. This covers the most common authentication attack vectors without overengineering for threats that do not apply to your threat model.

## Sources & Further Reading

- OWASP — Session Management Cheat Sheet — https://cheatsheetseries.owasp.org/cheatsheets/Session_Management_Cheat_Sheet.html
- Auth0 — JWT Handbook — https://auth0.com/resources/ebooks/jwt-handbook
- Verizon — 2024 Data Breach Investigations Report — https://www.verizon.com/business/resources/reports/dbir/

| Dimension | JWT | Session Auth |
| --- | --- | --- |
| State model | Stateless — verified by signature, no DB lookup | Stateful — server looks up session data (usually in Redis) |
| Revocation | Cannot invalidate before expiry without extra blacklist logic | Immediate — just delete the session record |
| Per-request overhead | None — signature check only, no network round trip | Small — Redis lookup adds about 1-5ms |
| Horizontal scaling | Easy — no shared storage required | Needs shared session storage (Redis) across servers |
| Implementation risk | Higher — algorithm=none and storage mistakes are common | Lower — fewer ways to misconfigure |
| Best fit | API-first apps, microservices, cross-service auth | Traditional server-rendered apps |

## Source links

- https://cheatsheetseries.owasp.org/cheatsheets/Session_Management_Cheat_Sheet.html
- https://auth0.com/resources/ebooks/jwt-handbook
- https://www.verizon.com/business/resources/reports/dbir/

## FAQ

### Why can't JWTs be revoked before they expire, and how do you work around this?

JWTs are stateless and self-contained, so the server has no central store to check whether a token has been invalidated — if the signature is valid and the expiry hasn't passed, the token is accepted. The practical workaround is to keep access token expiry short (15 minutes) and maintain a Redis blacklist of revoked token IDs (JTIs) for that window. For refresh tokens, storing the current valid hash in a database lets you revoke access by simply deleting the record.

### What is the alg=none vulnerability in JWTs and how do you prevent it?

Early JWT libraries allowed the algorithm field in the token header to be set to 'none', meaning no signature verification was required — an attacker could forge a token with any claims they wanted. Modern libraries have patched this, but only if configured correctly: you must explicitly whitelist the allowed algorithms (e.g., algorithms: ['HS256'] in jsonwebtoken) and never let the library read the algorithm from the token header itself.

### Why should JWT access tokens never be stored in localStorage?

Any JavaScript running on your page — including scripts injected via XSS — can read localStorage and steal tokens stored there. The recommended pattern is to keep the short-lived access token in a JavaScript variable (in memory) and the refresh token in an httpOnly Secure SameSite=Strict cookie. On page refresh, the app calls the refresh endpoint on initialization to obtain a new access token using the httpOnly cookie, providing both XSS resistance and CSRF resistance.

### What hybrid approach works well for ERP authentication?

A good combination is JWT access tokens with a 15-minute expiry (RS256 signed, stored in memory), refresh tokens with a 7-day expiry in httpOnly Secure cookies, refresh token rotation with a PostgreSQL-backed revocation table, and a Redis blacklist for the current 15-minute window. This gives the stateless verification speed of JWT while matching the revocation capability of traditional session auth, and supports 'log out everywhere' by deleting all refresh token records for a user ID.

### When should you choose session auth over JWT for a production system?

Session auth is the better fit when you need immediate revocation without engineering a separate blacklist, when the server-side session store (Redis) is already in your stack, or when simplicity of correct implementation is a priority — there are fewer footguns compared to JWT. JWT becomes advantageous for API-first or microservices architectures where stateless verification and cross-service authentication matter more than instant revocability.

---

- HTML version: https://www.matthewswong.com/en/blog/jwt-vs-session-auth-production/
- Bahasa Indonesia: https://www.matthewswong.com/id/blog/jwt-vs-session-auth-production/
- Site index for agents: https://www.matthewswong.com/llms.txt
- Sitemap: https://www.matthewswong.com/sitemap.xml
- API description (OpenAPI): https://www.matthewswong.com/openapi.json
