Development
JWT vs Session Authentication: Which to Use and When
A practical comparison of JWT and session-based authentication for web applications. Covers how each works, security trade-offs, scalability, token revocation, and a clear decision framework for choosing the right approach.
How Session Authentication Works
Session authentication is stateful. When a user logs in, the server creates a session record in a database or memory store and returns a session ID to the browser as a cookie. On every subsequent request, the browser sends that cookie and the server looks up the session ID in the store to find the associated user. The session store is the source of truth.
This model has been the standard for web applications for decades. Frameworks like Express.js with express-session, Django, Laravel, and Rails all use sessions by default. The session can hold any data — user ID, role, preferences — and that data is stored server-side. The cookie holds only the session ID, not the data itself.
// Session flow
// 1. Login: create session, return session ID in cookie
req.session.userId = user.id;
req.session.role = user.role;
// 2. Protected route: read from session
if (!req.session.userId) return res.status(401).json({ error: 'Not authenticated' });
// 3. Logout: destroy session
req.session.destroy();How JWT Authentication Works
JWT (JSON Web Token) authentication is stateless. When a user logs in, the server creates a signed token containing the user data (ID, role, expiry) and returns it to the client. On subsequent requests, the client sends the token in the Authorization header. The server verifies the signature cryptographically — no database lookup required. The token itself is the source of truth.
A JWT has three parts: a header specifying the algorithm, a payload containing the claims (user data), and a signature. The signature is computed using a secret key or private key. Anyone with the public key can verify the token is legitimate; only the server with the secret key can create new tokens. The payload is Base64-encoded, not encrypted — do not put sensitive data in a JWT unless you also encrypt it.
// JWT flow
// 1. Login: create and return token
const token = jwt.sign(
{ userId: user.id, role: user.role },
process.env.JWT_SECRET,
{ expiresIn: '15m' }
);
// 2. Protected route: verify token (no DB lookup)
const decoded = jwt.verify(token, process.env.JWT_SECRET);
req.user = decoded;The Token Revocation Problem
The most significant practical downside of JWTs is that you cannot revoke them before they expire. If a user logs out or an account is compromised, a session can be destroyed immediately. But a JWT continues to be valid until its expiry time because the server has no record of it. If a 24-hour JWT is stolen, the attacker has access for up to 24 hours.
The standard workaround is to use short-lived access tokens (15 minutes) paired with longer-lived refresh tokens stored in HttpOnly cookies. After 15 minutes, the client exchanges the refresh token for a new access token. If you need to revoke access immediately (account banned, password changed), you invalidate the refresh token in the database — but the access token remains valid for up to 15 minutes. For most applications, 15 minutes of residual access after revocation is an acceptable trade-off.
Security Considerations
Both approaches have security risks that are different in nature. Session cookies should be marked HttpOnly (not accessible to JavaScript), Secure (HTTPS only), and SameSite=Strict or Lax (CSRF protection). Store sessions in a database or Redis, not in-memory, for production deployments. Rotate the session ID after login to prevent session fixation attacks.
JWTs stored in localStorage are accessible to JavaScript and vulnerable to XSS attacks. A malicious script can read the token and send it to an attacker. JWTs stored in HttpOnly cookies are not accessible to JavaScript but require CSRF protection on state-changing requests. The consensus recommendation for browser-based applications is to store JWTs in HttpOnly cookies rather than localStorage.
- Sessions: use HttpOnly, Secure, SameSite=Strict cookies; rotate session ID on login
- JWTs in HttpOnly cookies: safe from XSS, need CSRF protection on POST/PUT/DELETE
- JWTs in localStorage: vulnerable to XSS, no CSRF risk but higher attack surface
- Both: always use HTTPS in production; HTTP sessions or tokens can be intercepted
- JWT algorithm: always specify alg explicitly; never accept none as a valid algorithm
Scalability and Architecture
Session authentication requires all requests to hit the session store, which means the session store becomes a shared dependency across all server instances. For a single server this is trivial. For a horizontally scaled application with multiple instances, you need a shared session store — typically Redis. This adds infrastructure complexity but is a well-understood pattern.
JWT authentication is stateless by design: any server instance can verify a token without talking to a shared store. This makes horizontal scaling simpler, and JWTs are the natural choice for microservice architectures where multiple independent services need to authenticate the same user. A single JWT issued by the auth service can be verified by the orders service, the billing service, and the notifications service without any of them sharing session state.
When to Use Sessions
Session authentication is the right choice for traditional server-rendered web applications where the frontend and backend are the same origin, for applications that require immediate revocation (banking, healthcare, enterprise security), and for teams that prefer a simpler mental model without managing token refresh logic.
Sessions also handle the logout flow cleanly: destroy the session server-side and the user is immediately and completely logged out. For consumer-facing applications where users frequently log out and back in, or where account security is critical, this immediate revocation is a meaningful advantage.
- Server-rendered applications where the API and frontend are the same domain
- Applications requiring immediate session revocation: banking, healthcare, admin tools
- Teams that want simpler auth logic without access/refresh token management
- Applications using established session middleware like express-session or Passport.js
When to Use JWTs
JWTs are the right choice for APIs consumed by multiple client types (web app, mobile app, third-party), for microservice architectures where multiple services need to verify the same user, and for cross-origin authentication where the API and frontend run on different domains.
JWTs are also well-suited for applications built on serverless or edge platforms that cannot maintain persistent connections to a session store. When your API needs to be stateless to scale horizontally without shared infrastructure, JWTs are the appropriate choice.
- REST APIs consumed by mobile apps, SPAs, or third-party clients
- Microservice architectures where multiple services verify the same identity
- Cross-origin APIs (frontend on app.company.com, API on api.company.com)
- Serverless and edge deployments where a session store cannot be maintained
The Access Token plus Refresh Token Pattern
Most production JWT implementations use two tokens. The access token is short-lived (15 minutes), stateless, and sent in the Authorization header. The refresh token is long-lived (7-30 days), stored in an HttpOnly cookie, and stored in the database so it can be revoked. When the access token expires, the client sends the refresh token to a dedicated endpoint and receives a new access token.
This pattern gives you the scalability of stateless JWTs for most requests, while maintaining the ability to revoke refresh tokens immediately when needed. Revoke a refresh token and the user will be fully logged out within 15 minutes. Add the user ID to a token blocklist and you can revoke access instantly at the cost of one database lookup per request — which is essentially session authentication applied only when needed.
FAQ
Can I use both JWT and sessions in the same application?
Yes, and this is sometimes the right approach. A common pattern is to use sessions for the main web application (same-origin, server-rendered or SPA) and JWTs for an API that third-party clients or mobile apps consume. Keep the two authentication layers separate with different middleware and different cookie domains.
Is JWT more secure than sessions?
Neither is inherently more secure. Both can be implemented securely or insecurely. Sessions stored in Redis with HttpOnly, Secure, SameSite cookies are very secure. JWTs in HttpOnly cookies with short expiry are also secure. JWTs in localStorage are less secure than either cookie-based approach. The implementation matters more than the choice of mechanism.
Should I put the user role and permissions in the JWT?
Storing the role in the JWT is common and convenient — it allows authorization checks without a database lookup. The trade-off is that the role in the token is only updated when a new token is issued. If you change a user role, the old token reflects the previous role until it expires. For short-lived tokens (15 minutes) this is usually acceptable. For sensitive permission changes, invalidate the refresh token to force a full re-authentication.
What signing algorithm should I use for JWTs?
For most applications, HS256 (HMAC with SHA-256) is sufficient and simple: one shared secret key for signing and verifying. Use RS256 (RSA) or ES256 (ECDSA) when multiple services verify tokens but only one service issues them — the issuer keeps the private key and each verifier gets the public key. ES256 with a 256-bit key is more efficient than RS256 with a 2048-bit key for high-volume verification.
Related free tools
If you want to turn this topic into action, use one of ShortIQ's free tools for campaign planning, UTM structure, or QR distribution.
Continue Reading
Explore more guides on link shortener SaaS strategy, Bitly alternatives, and white label link management.
Free newsletter
Get new guides in your inbox
We publish practical guides on dev tooling, prompt engineering, marketing workflows, and deployment. No fluff — straight to the point.
No spam. Unsubscribe any time.
Was this article helpful?
Tell us if this guide solved the problem or what was still missing. We use this to improve the blog and only follow up if you explicitly allow it.