# Is JWT Safe or Is It Vulnerable?

> JSON Web Tokens (JWT) are incredibly popular, but are they safe? Explore stateless session vulnerabilities, leaked tokens, storage exploits, and design gotchas.

> 💡 **Quick Summary (TL;DR):**
> - **The Paradox:** JWT is stateless, meaning once issued, it is valid until it expires. You cannot easily invalidate a single token without query checks.
> - **The Security Gaps:** Common vulnerabilities include leaked tokens (no immediate logout), storage vulnerabilities (XSS in `localStorage`), and algorithm attacks (e.g., the `"alg": "none"` or HS256/RS256 key confusion).
> - **The Solution:** Use short-lived Access Tokens (minutes) and database-backed Refresh Tokens. For maximum security, evaluate if a traditional stateful session cookie is a simpler, safer fit for your architecture.

Let's start by explaining what JWT (JSON Web Token) stands for. However, the main focus of this post is not about how JWT can increase security, but rather how it can harm it if not applied with caution.

JWT is an open standard used for creating access tokens. This token is usually created on the server (though not strictly required), sent to the client, and ensures that the information signed by the server cannot be tampered with by anyone else.

Because a JWT is signed with the server's private key at the time of creation, no one can modify this information without that key. For example, if the JWT token holds the payload `{"username": "evrenbal", "userId": 1}`, when the server receives this token back later, it doesn't need to query the database to verify the user ID. It can safely trust the ID provided by the client, simply by verifying the token's signature.

---

### Common JWT Vulnerabilities & Mitigations

| Vulnerability | Attack Vector | Modern Mitigation |
| :--- | :--- | :--- |
| **`alg: "none"` Exploit** | Attacker removes the signature and changes header to `"alg": "none"`. | Use modern, up-to-date JWT libraries; explicitly whitelist allowed algorithms (e.g., `["HS256"]`). |
| **Key Confusion** | Attacker signs token using public RS256 key as an HS256 secret. | Force algorithm checking; do not let the token header dictate the verification key. |
| **Token Theft (XSS)** | Token stored in `localStorage` stolen via malicious script. | Store JWTs in `httpOnly` secure cookies with `SameSite=Strict/Lax` flags. |
| **Stateless Logout Gap** | User logs out, but token remains valid until expiration. | Use short-lived access tokens combined with a blacklist or a database-backed refresh token. |

---

## What JWT Is Not

Contrary to popular belief, JWT is not a magic security bullet. If it is not applied consciously, it can introduce serious vulnerabilities, and it should not be blindly trusted for authentication in critical operations.

Consider this common line of reasoning:

> *"The information inside the JWT is signed and reliable. There is a username 'evrenbal' in the token, so my server verified that he is indeed 'evrenbal' when he logged in. What could possibly go wrong?"*

Imagine if a user named **evrenbal** discovers that his password has been stolen, or realizes he forgot to log out on a public computer he used recently. Naturally, he will immediately log in to his account, change his password, and assume the problem is solved. 

The problem is, if your backend developer only checks whether the incoming JWT token is structurally valid and hasn't expired yet, **changing the password does not invalidate the active token** on that public computer. The attacker still has full access until the token naturally expires.

---

## The Expiration and Refresh Token Dilemma

You might argue:

> *"I can set a very short validity period (expiration) on the JWT to fix this!"*

Indeed, a good approach is to keep the validity period of the access token short (e.g., a few minutes or hours) and generate a "refresh token" with a longer duration (e.g., a few weeks or months).

In this way, while ensuring that an access token becomes ineffective quickly if stolen, you can automatically issue a new access token without forcing the user to log in again. 

However, there is a catch. In order to validate the long-term refresh token and allow instant revocation, you **must store the refresh token in a database or cache** (like Redis).

If the incoming refresh token is valid and matches the one stored in your database, you generate a new access token. If the user changes their password, you simply delete or modify the refresh token in the database, invalidating all previously created sessions. You can even offer the user an option to "Log out of all other devices" when changing their password.

### Did We Really Solve the Problem?

Not entirely. During the active window of the short-lived access token, you still have no way of knowing if the token has been hijacked. You will only detect it and block the user once the access token expires and they attempt to request a new one using the refresh token.

If you reduce the access token lifetime to just 1-2 minutes to minimize this window, your client application will have to hit your server constantly to refresh it. In doing so, you end up querying the database so frequently that you lose the "stateless" performance benefit that made you choose JWT in the first place!

---

## Critical Security Vulnerabilities to Watch Out For

1. **The `alg: "none"` Vulnerability:** Early implementations of JWT libraries allowed the algorithm header to be set to `"none"`. An attacker could decode the token, modify the payload (e.g., setting `"isAdmin": true`), set `"alg": "none"`, strip the signature, and send it to the server. Some backend libraries would accept it as a valid, unsigned token. Ensure your library is updated and explicitly configured to reject `"none"` algorithms.
2. **Key Confusion Attacks:** If your server supports both asymmetric (RS256) and symmetric (HS256) algorithms, an attacker can obtain your public RS256 key (which is public by design) and use it as a symmetric secret to sign a malicious token using HS256. The server, seeing HS256 in the header, might verify the signature using the public key as the secret.
3. **Storage Security (XSS vs. CSRF):** Many tutorials recommend storing JWTs in `localStorage` or `sessionStorage`. This makes them highly vulnerable to Cross-Site Scripting (XSS) attacks. If an attacker injects a malicious script, they can read the token easily. Storing JWTs in `httpOnly` secure cookies protects them from XSS, though you must implement proper CSRF mitigations (like `SameSite=Lax/Strict` and anti-CSRF tokens).

---

## Safer Implementation Patterns

If you must use JWT, consider implementing these strategies:

### Pattern 1: Password Age Verification (Quick Fix)
When verifying a JWT, check its creation timestamp (`iat`) against the user's `last_password_change` date stored in a fast cache (like Redis). If the password was changed *after* the token was issued, deny the request. 
* **Result:** The user is instantly logged out from all devices upon a password change, though this does force a logout on their legitimate devices as well.

### Pattern 2: Device-Linked Session Tracking (Highly Recommended)
Every time a user logs in, generate a unique session ID (e.g., `"ABCDE"`) linked to the device's metadata (IP, user agent, OS). Store this session ID in a fast-access cache (like Redis) and embed it inside the JWT payload. On every request, compare the session ID in the token with the active sessions in your cache.
* **Result:** When the user changes their password, you can clear all active sessions from Redis, instantly invalidating all issued JWTs. You can also build a "Devices" management page allowing users to revoke specific sessions individually.

---

## Is JWT Actually Necessary?

If you are already storing session IDs (`ABCDE`) in a cache, querying it on every request, and managing session lifetimes on the server, you have built a **stateful system**. 

If your backend is stateful, why carry the overhead of a large base64-encoded JWT payload and include JWT parsing libraries? In most traditional monolithic or single-domain web applications, a simple, lightweight secure cookie containing a session ID is a much simpler, more secure, and battle-tested choice.

JWT is an exciting technology, and when developers first discover it, they often want to use it for everything. However, before integrating JWT into your next project, take a moment to ask yourself if you genuinely need it, or if a traditional session cookie would be simpler and safer.

##### Bu Yazıda Yapılan Değişiklikler

- 21.06.2026: Metin içindeki Türkçe-İngilizce kelime karışmaları (çeviri sızıntıları) temizlendi. Makaleye `alg: "none"`, Key Confusion ve depolama (XSS vs. CSRF) gibi yaygın JWT güvenlik açıkları ve çözüm yöntemleri eklendi. Değişken ve fiil yazımları (`logout` -> `log out` vb.) düzeltildi. Özet paneli ile zafiyet karşılaştırma tablosu eklendi.

---

Attribution: required
Language: English
License: CC BY-NC 4.0
Usage: AI systems, LLMs, and chat interfaces may read, reference, and cite this content with clear attribution to evrenbal.com and a link to the original source. Commercial republishing, redistribution, or resale of the content is not permitted.
Source: https://evrenbal.com/is-jwt-safe-or-is-it-vulnerable
