DEV Community

Cover image for 10 JWT Mistakes That Compromise Security
Arunangshu Das
Arunangshu Das

Posted on

10 JWT Mistakes That Compromise Security

In the fast-paced world of web and mobile development, JWT (JSON Web Tokens) has become the de facto standard for managing authentication and authorization. It’s stateless, easy to implement, and widely supported across frameworks. But here's the problem: JWTs are not bulletproof by default—and small mistakes can easily turn into huge vulnerabilities.

If you're a developer using JWTs in your Node.js, Express, Python, or even mobile app backends—this post is for you.

1. Using "none" as the Algorithm (alg)

The Mistake:

One of the most infamous JWT vulnerabilities involves the use of "alg": "none"—which essentially tells the JWT validator: “Hey, don’t bother verifying the signature!”

Some insecure JWT libraries or misconfigured setups will accept such tokens and treat them as valid.

Why It’s Dangerous:

If an attacker crafts a token and sets "alg": "none", and your server accepts it without verifying, you’ve given them admin access.

How to Fix It:

  • Explicitly define which algorithms are allowed in your JWT validation library.
  • Disable or disallow the none algorithm.
  • In jsonwebtoken (Node.js), do this:
jwt.verify(token, secretKey, { algorithms: ['HS256'] });
Enter fullscreen mode Exit fullscreen mode

2. Storing JWTs in LocalStorage

The Mistake:

Many frontend tutorials suggest storing JWTs in localStorage. It's easy, it works—and it's a huge security risk.

Why It’s Dangerous:

LocalStorage is vulnerable to XSS (Cross-Site Scripting). If an attacker injects malicious JavaScript into your site, they can read tokens and impersonate users.

How to Fix It:

  • Prefer HTTP-only, Secure cookies to store JWTs. These can’t be accessed by JavaScript.
  • Always sanitize and validate user input to prevent XSS attacks.
// Example of setting JWT in a secure cookie (Node.js + Express)
res.cookie('token', jwtToken, {
  httpOnly: true,
  secure: true,  // only over HTTPS
  sameSite: 'Strict',
});
Enter fullscreen mode Exit fullscreen mode

3. Not Setting an Expiry (exp) Claim

The Mistake:

JWTs are stateless—meaning once they’re issued, they remain valid until they expire. If you forget to set an expiry time, the token lives forever.

Why It’s Dangerous:

Long-lived tokens can be stolen and reused indefinitely. This increases the attack surface.

How to Fix It:

Always set a reasonable expiration time. Examples:

  • Access tokens: 15 minutes
  • Refresh tokens: 7 days
jwt.sign(payload, secret, { expiresIn: '15m' });
Enter fullscreen mode Exit fullscreen mode

Also consider using short-lived tokens + refresh token rotation for enhanced security.

4. Using Weak or Predictable Secrets

The Mistake:

JWTs use secrets (in HS256, for example) to sign and verify tokens. But many developers use short or guessable secrets like "mysecret" or "1234".

Why It’s Dangerous:

An attacker could brute-force your secret and generate valid tokens themselves. That’s game over.

How to Fix It:

  • Use long, random secrets (e.g., at least 256 bits).
  • Store secrets securely using environment variables.
  • For production, generate secrets using openssl or password managers.
openssl rand -hex 32
Enter fullscreen mode Exit fullscreen mode

And never hardcode your secret in code like:

const secret = 'superweaksecret'; // ❌ Don't do this!
Enter fullscreen mode Exit fullscreen mode

5. Accepting Tokens Signed with Different Algorithms

The Mistake:

Some JWT libraries auto-detect the algorithm used in the incoming token. So even if you usually use RS256, the library might still accept a token signed with HS256 if it sees one.

Why It’s Dangerous:

This opens the door to algorithm confusion attacks. Attackers could forge a token with HS256 and sign it using your public key as the secret.

How to Fix It:

  • Explicitly declare accepted algorithms.
  • Validate that the JWT algorithm matches your expectation.
jwt.verify(token, publicKey, {
  algorithms: ['RS256'], // explicitly set
});
Enter fullscreen mode Exit fullscreen mode

6. Trusting JWTs Without Validation

The Mistake:

Some devs decode JWTs without verifying the signature at all—assuming if it looks okay, it is okay.

const payload = jwt.decode(token); // Insecure: no validation
Enter fullscreen mode Exit fullscreen mode

Why It’s Dangerous:

Decoding does not verify that the token is legitimate or untampered. Anyone could change the payload and pass it off as valid.

How to Fix It:

  • Use jwt.verify() to decode and validate the token.
  • Always ensure the token hasn’t been altered and is from a trusted issuer.

7. Exposing Sensitive Data in the Payload

The Mistake:

JWT payloads are base64-encoded—not encrypted. Many developers mistakenly put passwords, personal data, or even credit card info in them.

Why It’s Dangerous:

Anyone who gets access to the JWT (e.g., via browser storage or logs) can decode and read the payload.

How to Fix It:

  • Only include non-sensitive, minimal data in JWTs (e.g., user ID, roles).
  • If you need to transmit sensitive data, encrypt it separately.
  • Consider using JWE (JSON Web Encryption) if encryption is absolutely necessary.

8. Not Rotating Refresh Tokens

The Mistake:

You might issue long-lived refresh tokens to allow users to get new access tokens. But if you're not rotating them (i.e., issuing a new one each time), you’re vulnerable to replay attacks.

Why It’s Dangerous:

If a refresh token is stolen, it can be reused indefinitely unless you have token rotation in place.

How to Fix It:

  • Implement refresh token rotation: each time a new access token is issued, also issue a new refresh token and invalidate the old one.
  • Store refresh tokens in a database with metadata (user ID, expiry, last used).
  • Invalidate old tokens immediately if reuse is detected.

9. Skipping Audience (aud) and Issuer (iss) Claims Validation

The Mistake:

You receive a JWT, decode it, and trust the payload—but you don’t validate who issued it or who it was intended for.

Why It’s Dangerous:

Anyone could issue a JWT from another system or fake app, and your backend might accept it blindly.

How to Fix It:

  • Always validate both aud (audience) and iss (issuer) claims to ensure the token is meant for your app and issued by your auth server.
jwt.verify(token, secret, {
  audience: 'your-app',
  issuer: 'your-auth-server',
});
Enter fullscreen mode Exit fullscreen mode

10. Over-relying on JWT for Session Management

The Mistake:

Some devs treat JWTs like traditional sessions—logging users out by “deleting the token” on the frontend.

Why It’s Dangerous:

JWTs are stateless. You can’t “delete” them from the server if you don’t track them somewhere. Once issued, they remain valid until they expire.

How to Fix It:

  • Maintain a token revocation list (in Redis, DB, etc.) for high-value apps.
  • Consider using blacklisting or whitelisting strategies.
  • Or use traditional session-based auth if you need full control.

Bonus: Best Practices for JWT Security

Here’s a quick summary of JWT security best practices you should follow:

→ Use strong, long secrets or key pairs
→ Always validate the JWT signature
→ Use HTTP-only, secure cookies (not localStorage)
→ Set short expiry times
→ Rotate refresh tokens
→ Validate alg, aud, and iss claims
→ Never store sensitive data in JWT payloads
→ Use encryption only when necessary
→ Sanitize all inputs to prevent XSS
→ Prefer libraries with active maintenance and updates

Final Thoughts

JWTs can be powerful, scalable, and efficient—but only when used responsibly. Most security vulnerabilities in JWT implementations are the result of misconfiguration, shortcuts, or simply not understanding how tokens work under the hood.

As developers, we’re responsible for making authentication secure—not just functional.

You may also like:

  1. Top 10 Large Companies Using Node.js for Backend

  2. Why 85% of Developers Use Express.js Wrongly

  3. Top 10 Node.js Middleware for Efficient Coding

  4. 5 Key Differences: Worker Threads vs Child Processes in Node.js

  5. 5 Effective Caching Strategies for Node.js Applications

  6. 5 Mongoose Performance Mistakes That Slow Your App

  7. Building Your Own Mini Load Balancer in Node.js

  8. 7 Tips for Serverless Node.js API Deployment

  9. How to Host a Mongoose-Powered App on Fly.io

  10. The Real Reason Node.js Is So Fast

  11. 10 Must-Know Node.js Patterns for Application Growth

  12. How to Deploy a Dockerized Node.js App on Google Cloud Run

  13. Can Node.js Handle Millions of Users?

  14. How to Deploy a Node.js App on Vercel

  15. 6 Common Misconceptions About Node.js Event Loop

  16. 7 Common Garbage Collection Issues in Node.js

  17. How Do I Fix Performance Bottlenecks in Node.js?

  18. What Are the Advantages of Serverless Node.js Solutions?

  19. High-Traffic Node.js: Strategies for Success

Read more blogs from Here

Share your experiences in the comments, and let's discuss how to tackle them!

Follow me on LinkedIn

Top comments (0)