Stop Using JWTs for Your Web App Sessions
Learn why JSON Web Tokens (JWTs) might be compromising your application's security and why traditional session management is often the better choice for developers.

- NV Trends
- 9 min read

If you have spent any time in the Indian developer community over the last five years, you have likely encountered the “MERN stack” or similar JavaScript-heavy frameworks. Whether you are browsing tutorials on YouTube or attending a coding bootcamp in Bengaluru, there is one piece of advice that seems universal: “Use JWTs for authentication.”
JSON Web Tokens (JWTs) have become the default choice for modern web applications. They are marketed as the “stateless” savior of the internet, promising infinite scalability and a break from the “clunky” old ways of server-side sessions. But as the tech industry matures and security threats become more sophisticated, many senior architects are sounding the alarm. The truth is that for the vast majority of web applications—from your local e-commerce startup to internal corporate portals—JWTs are not just unnecessary; they are often a security liability.
In this guide, we will peel back the hype and look at the technical realities of JWTs. We will explore why they are so popular, the fundamental flaws they introduce to your security architecture, and why you should probably go back to using traditional session IDs for your next project.

What is a JWT, Really?
Before we dive into the “why,” we need to understand the “what.” A JSON Web Token is a compact, URL-safe means of representing claims to be transferred between two parties. It consists of three parts separated by dots: a Header, a Payload, and a Signature.
The Header typically tells the server which algorithm is being used to sign the token (like HS256). The Payload contains the “claims”—this is where you store the user ID, their role, and the expiration date. Finally, the Signature is created by taking the encoded header and payload, a secret key, and running them through the specified algorithm.
The core appeal of the JWT is that it is self-contained. When a user logs in, the server creates this token and sends it to the client. The client then sends this token back with every subsequent request. Because the server has the secret key, it can verify the signature to ensure the data hasn’t been tampered with. The server doesn’t need to check a database to see if the user is “logged in”—the token itself is the proof.
The “Stateless” Trap
The primary reason developers reach for JWTs is the promise of statelessness. In a traditional session-based system, the server generates a random “Session ID” and stores it in a database or a cache like Redis. When the client sends that ID, the server looks it up to find out who the user is.
Proponents of JWTs argue that this lookup is a “bottleneck.” They claim that by moving the session data to the client (inside the JWT), you save a database hit and make your app “infinitely scalable.”
But let’s look at the reality of a growing Indian startup. Suppose you are building a fintech app where users can manage their investments. Even if you have 100,000 active users, a modern Redis instance can handle tens of thousands of lookups per second with sub-millisecond latency. The “cost” of a session lookup is negligible compared to the overhead of your actual business logic or complex SQL queries.
By chasing “statelessness,” you are often optimizing for a problem you don’t actually have, while introducing several much larger problems.
The Revocation Nightmare
This is the single biggest technical flaw with using JWTs for web sessions: You cannot easily revoke them.
Imagine a user named Arjun. Arjun is using your app on his phone while commuting on the Delhi Metro. Suddenly, his phone is stolen. The first thing Arjun does is find another device and try to “Log out of all devices” or change his password.
In a traditional session-based system, this is easy. The server simply deletes all session records associated with Arjun’s user ID from the database. The next time the thief tries to use the stolen phone, the session ID will be invalid, and they will be kicked out.
With a stateless JWT, the token is out “in the wild.” It has an expiration date (say, 7 days). Because the server doesn’t keep a list of active tokens, it has no way to tell the stolen phone that the token is no longer valid. The thief could potentially have access to Arjun’s account—and his Rs. 50,000 investment portfolio—for the remainder of those 7 days.
The “Blacklist” Irony
To solve this, developers often implement a “Blacklist.” Every time a user logs out or changes their password, the server adds that specific JWT to a Redis database. Then, for every request, the server checks if the incoming JWT is on the blacklist.
Do you see the irony? By adding a blacklist, you have just re-introduced state into your “stateless” system. You are now doing a database lookup for every request anyway, but with more complex code and a larger, more cumbersome token. You have the worst of both worlds.
Security: The Hidden Vulnerabilities
JWTs are significantly more complex to implement securely than traditional sessions. Because they are a standard with many options, they provide more “foot-guns” for developers.
1. The “None” Algorithm
Early versions of many JWT libraries had a critical flaw. A header could specify "alg": "none". If the server didn’t explicitly forbid this, an attacker could create a JWT with a payload saying "admin": true, set the algorithm to “none,” and bypass the signature check entirely. While most modern libraries have patched this, it highlights the inherent complexity of the spec.
2. Storage: XSS vs. CSRF
Where do you store a JWT on the client?
- LocalStorage: This is the most common advice in “beginner” tutorials. It is also the most dangerous. Any JavaScript running on your page (including third-party analytics or a compromised npm package) can read
localStorage. This makes your token a prime target for Cross-Site Scripting (XSS) attacks. - Cookies: If you store the JWT in a cookie with the
httpOnlyandSecureflags, JavaScript cannot read it. This protects you from XSS. However, cookies are vulnerable to Cross-Site Request Forgery (CSRF).
If you use a traditional session ID, you store it in an httpOnly cookie and use a standard CSRF token. This is a battle-tested, secure pattern that has worked for decades. When you use a JWT in a cookie, you still have to deal with CSRF, but you are also sending a much larger payload (the full JWT) in every request header, which can occasionally hit header size limits on load balancers.
The Performance Reality Check
We are often told that JWTs are faster because they avoid a database hit. However, verifying a JWT is not “free.” It requires cryptographic operations (HMAC or RSA/ECDSA) that are CPU-intensive.
In contrast, looking up a session ID in a memory-cached store like Redis is incredibly fast. For most applications, the CPU time spent verifying a complex JWT signature is actually higher than the network and processing time of a Redis lookup.
Furthermore, JWTs are much larger than session IDs. A session ID might be 32 characters. A JWT containing user metadata can easily be 500 to 1,000 characters. In a world where many Indian users are still on inconsistent 4G or 5G connections, every extra kilobyte in your request headers adds to the latency of your app.
The Complexity of Refresh Tokens
To mitigate the “Revocation Problem,” developers often use very short-lived access tokens (e.g., 15 minutes) and longer-lived “Refresh Tokens.”
This adds massive complexity to your frontend. Your React or Flutter app now needs logic to:
- Detect when an access token has expired.
- Pause all outgoing requests.
- Call a “Refresh” endpoint.
- Update the stored tokens.
- Resume the original requests.
If the refresh fails, you have to handle the logout flow gracefully. Compare this to traditional sessions: the browser handles the cookie automatically, and if the session expires, the server simply returns a 401. No complex client-side state management is required.
When Should You Actually Use JWTs?
It is important to note that JWTs aren’t “evil”—they are just frequently misused. There are specific scenarios where they are the correct tool:
1. Microservices and Server-to-Server Auth
In a large-scale architecture where Service A needs to talk to Service B, and they don’t share a database, a JWT is perfect. Service A can sign a token that Service B can verify using a public key. This allows for secure communication without a centralized session store.
2. Single-Use Tokens
If you need to send a “Password Reset” link via email or a “Download File” link that expires in 10 minutes, a JWT is a great choice. It carries the necessary data and the expiration time in the URL itself, removing the need to track these one-time events in your database.
3. Federated Identity (OAuth2 / OpenID Connect)
When you use “Login with Google,” Google issues an ID Token (which is a JWT). This is a standard way for a third party to tell your application, “I have verified that this user is who they say they are.” However, once your app receives that info, you should still create a local session for the user rather than passing that JWT around for the rest of their visit.
Practical Advice for Indian Developers
If you are currently building an app—whether it’s a simple “Kirana store” management tool or a high-traffic media portal—here is the recommended path:
- Default to Sessions: Use your framework’s built-in session management (e.g.,
express-sessionfor Node.js, Django’s built-in sessions, or Spring Security). - Use Redis for Scaling: If you find that a single server cannot handle your session load, move the session store to a managed Redis instance (like those provided by AWS, Google Cloud, or even local providers).
- Secure Your Cookies: Always use
httpOnly,Secure, andSameSite=Lax(orStrict) flags for your session cookies. - Keep it Simple: Don’t implement Refresh Token rotation and JWT blacklisting unless you have a very specific architectural requirement that mandates it.
Conclusion
The “Stop Using JWTs” movement isn’t about being “old school”—it’s about choosing the right tool for the job. JWTs were designed for a world of distributed systems and federated identity, not for simple browser-to-server user sessions.
By sticking with traditional sessions, you get instant revocation, better protection against XSS, and a significantly simpler codebase. In the fast-paced world of Indian tech, where time-to-market and security are paramount, simplicity is your greatest competitive advantage. Save the JWTs for your microservices, and let your web sessions be handled by the tools that were built for them.
