JSON Web Token Security Explained
JWTs are everywhere in modern web apps. They’re simple, fast, and convenient, which is exactly why they’re so easy to misuse.
JWTs aren’t insecure by default. But they are very easy to implement badly. Most JWT-related security issues don’t come from the concept itself, but from assumptions developers make about how tokens behave.
What is a JWT?
A JSON Web Token is just a compact way of sending signed data between a client and a server.
The important part is this: the server trusts the data inside the token because it can verify that the token was signed correctly. If that trust breaks, everything breaks.
JWTs are commonly used for authentication, session handling, and API access.
Structure of a JWT
A JWT consists of three base64-encoded parts separated by dots:
header.payload.signature Each part has a specific purpose.
Header The header describes how the token is signed. It usually includes the token type and the algorithm used, like HS256 or RS256.
Payload The payload contains the actual data. This could be user IDs, roles, permissions, timestamps, or anything else the server needs. The payload is not encrypted, only encoded.
Signature The signature is what makes JWTs trustworthy. It’s created using the header, the payload, and a secret or private key. If any part of the token changes, the signature should no longer be valid.
How JWTs Are Used
When a user logs in, the server generates a JWT and sends it to the client.
The client then stores the token and includes it in every future request. The servers job is to verify the signature and trust the data inside the token without needing to query the database for every request.
This stateless nature is why JWTs scales so well. But this also means that mistakes can be very dangerous.
Why People Use JWTs
People use JWTs for a few reasons:
They’re fast
No session lookups on every request.
They scale well
No server-side session storage required.
They’re flexible
You can include whatever claims you need.
But none of this matters if the token can be forged.
Best Practices for JWT Security
JWT security mostly comes down to discipline.
Always use HTTPS
Always use HTTPS to protect JWTs in transit. This prevents token interception and ensures the integrity of the security token as it flows between the client and server.
Keep tokens short-lived
The longer a token is valid, the higher the risk of it being misused. Implement short expiration times for tokens and consider using refresh tokens to maintain sessions over longer periods securely.
Store tokens safely
On the client side, store JWTs securely to prevent Cross-Site Scripting (XSS) attacks. Avoid storing tokens in local storage and prefer HTTPOnly cookies or other secure mechanisms.
Validate everything
Always validate JWTs to ensure they conform to the expected format and contain all necessary claims. This helps in preventing injection attacks and other forms of tampering.
Use strong algorithms and keys
For signing JWTs, use strong keys and secure algorithms. Prefer asymmetric algorithms like RSA or ECDSA for public applications, where the signing key is public and the verification key is kept private.
RS256 for public-facing systems.
Common JWT Vulnerabilities
JWT issues usually come from incorrect validation. Here are some common ones:
Weak keys
Weak or easily guessable keys can lead to JWT forging. Make sure that keys are complex and securely stored.
None Algorithm Vulnerability
Some implementations may accept the "alg": "none" in the header, which can bypass signature verification. Ensure your implementation does not trust tokens with none as the algorithm.
Algorithm confusion
Attackers can modify the JWT header to indicate that the token is unsigned. Always verify that the token’s algorithm matches the expected algorithm.
XSS and CSRF Vulnerabilities
Improper token storage can make JWTs vulnerable to XSS attacks, while CSRF attacks can exploit authenticated sessions. Implement appropriate countermeasures, such as using secure cookies and CSRF tokens.
Example of the None Algorithm Vulnerability
One of the most infamous JWT vulnerabilities is trusting "alg": "none".
If a server accepts a token that claims it has no signature, attackers can modify the token freely.
Let’s say we start with a normal JWT:

We decode it using jwt.io and see the header:
{
"alg": "HS256",
"typ": "JWT"
} And the payload:
{
"id": "1",
"name": "John Doe",
"role": "user",
"iat": 1707961757
} The server trusts this token because it believes the signature is valid.
Now we change the header to:
{
"alg": "none"
} We can use base64encode to get the base64 encoded string: ewoiYWxnIjogIk5vbmUiCn0=.
Then we simply replace the original header with the newly encoded header.
In case some changes to the payload are needed, we can use base64decode to decode the payload, make the changes and then encode it again. In this case I simply changed the users role from user to admin making John an admin.
{
"id": "1",
"name": "John Doe",
"role": "admin",
"iat": 1707961757
} Because the algorithm is set to none, no signature is expected, so the signature can be removed entirely.
If the server blindly trusts the token, it will accept it.

And just like that, a regular user could theoretically become an admin.
Final Thoughts
JWTs are not just some magic security tokens. They’re just signed data that can be easily forged if not validated correctly. So if they are validated correctly, they are solid and efficient. If they are not validated correctly, they become a liability very quickly.
