Security

JWT Token Security: What Every Developer Gets Wrong

The Debuggers Engineering Team
10 min read

Digital security padlock on a circuit board representing JWT token protection

JSON Web Tokens (JWTs) are everywhere. They power authentication in single-page applications, mobile apps, and microservice architectures. But while JWTs are elegant in design, they are dangerously easy to misuse. A single misconfiguration can expose your entire system to session hijacking, privilege escalation, or data leaks.

This guide breaks down the most common JWT security mistakes and shows you how to fix each one. If you work with authentication in any capacity, this is required reading.

How JWTs Actually Work

Before diving into the mistakes, let us establish how a JWT functions. A JWT is a Base64URL-encoded string split into three parts: header, payload, and signature.

The header declares the algorithm used for signing (e.g., HS256, RS256). The payload contains claims - pieces of data like the user ID, role, and expiration time. The signature is a cryptographic hash that verifies the token has not been tampered with.

Here is the critical point: JWTs are signed, not encrypted. Anyone who intercepts a JWT can read the payload. The signature only guarantees integrity, not confidentiality.

JWT token structure showing header, payload, and signature parts

Mistake 1: Using the "none" Algorithm

The JWT specification includes an alg: "none" option that disables signature verification entirely. Attackers exploit this by modifying a JWT header to use "none", stripping the signature, and sending the modified token to your server.

If your JWT library does not explicitly reject alg: "none", an attacker can forge any token they want.

The Fix

Always validate the algorithm server-side. Never let the incoming token dictate which algorithm to use. Hardcode the expected algorithm in your verification logic:

jwt.verify(token, secret, { algorithms: ['HS256'] });

Mistake 2: Weak or Hardcoded Secrets

Using "secret", "password", or any guessable string as your HMAC secret is equivalent to having no security at all. Attackers use brute-force tools like hashcat to crack weak JWT secrets in minutes.

The Fix

Generate a cryptographically random secret of at least 256 bits. Store it in environment variables, never in source code.

openssl rand -base64 32

For production systems, consider using RSA or ECDSA key pairs (RS256/ES256) instead of symmetric HMAC secrets. This way, only the server with the private key can sign tokens, while any service with the public key can verify them.

Mistake 3: Storing JWTs in localStorage

This is the most debated topic in frontend security. Storing JWTs in localStorage makes them accessible to any JavaScript running on the page. A single XSS vulnerability - even from a third-party script - can steal every token.

The Fix

Use httpOnly cookies with Secure and SameSite=Strict attributes. These cookies are invisible to JavaScript, which makes XSS token theft impossible.

Set-Cookie: token=eyJhbG...; HttpOnly; Secure; SameSite=Strict; Path=/

If cookies are not an option, keep tokens in memory (a JavaScript variable) and use refresh tokens stored in httpOnly cookies to re-issue access tokens on page reload.

Mistake 4: Not Setting Expiration

Tokens that never expire are permanent keys to your system. If one is stolen, the attacker has indefinite access.

The Fix

Set short expiration times for access tokens (5–15 minutes) and use refresh tokens for session continuity:

{
  "sub": "user123",
  "role": "admin",
  "exp": 1709730000,
  "iat": 1709729100
}

Implement a token refresh flow: when the access token expires, the client uses a long-lived refresh token (stored in an httpOnly cookie) to obtain a new access token without re-authenticating.

Authentication flow diagram showing token refresh mechanism

Mistake 5: Trusting the Payload Without Verification

JWTs are Base64-encoded, not encrypted. Never use the payload data without first verifying the signature. Some developers decode the payload on the client or in middleware without checking whether the signature is valid - this defeats the entire purpose of signing.

The Fix

Always verify the full token before using any claims from the payload. In Express.js:

const decoded = jwt.verify(token, process.env.JWT_SECRET, {
  algorithms: ['HS256'],
  issuer: 'your-app-name'
});

Mistake 6: Not Implementing Token Revocation

JWTs are stateless by design - once issued, they are valid until they expire. But what happens when a user logs out, changes their password, or gets their account compromised? Without revocation, the stolen token remains valid.

The Fix

Implement one of these strategies:

  1. Short-lived tokens (5 min) with refresh token rotation
  2. Token blacklist in Redis - store revoked token JTIs until expiry
  3. Token versioning - store a version counter per user and reject tokens with old versions

Mistake 7: Putting Sensitive Data in the Payload

Since JWTs are not encrypted, any data in the payload is visible to anyone who intercepts the token. Do not include passwords, credit card numbers, personal identifiable information, or internal system details.

The Fix

Include only the minimum necessary claims: user ID, role, and expiration. If the consumer needs more data, they should call a protected API endpoint using the token for authentication.

Best Practices Summary

PracticeWhy It Matters
Reject alg: "none"Prevents unsigned token attacks
Use strong random secretsStops brute-force cracking
Store in httpOnly cookiesBlocks XSS token theft
Set short expiration (5-15 min)Limits exposure window
Verify before trustingEnsures token integrity
Implement revocationHandles logout and compromise
Minimise payload dataReduces information leakage

Test Your JWT Implementation

Before deploying any JWT-based authentication, paste your tokens into our free JWT Debugger to inspect headers, payloads, and expiration times. It runs entirely in your browser - your tokens are never transmitted to any server.

Understanding token structure visually is the fastest way to catch misconfigurations before they reach production.

Frequently Asked Questions

Is JWT safe for authentication?

JWT is safe when implemented correctly - with strong secrets, short expiration, httpOnly cookie storage, and proper signature verification. Most JWT security breaches stem from implementation errors, not flaws in the standard itself.

Should I use JWT or session cookies?

For traditional server-rendered applications, session cookies are simpler and more secure. JWTs shine in distributed systems and SPAs where stateless authentication reduces server load. Choose based on your architecture.

Can a JWT be decoded without the secret key?

Yes. The header and payload are Base64URL-encoded, not encrypted. Anyone can decode and read them. The secret key is only needed to verify the signature - it proves the token was not tampered with.

Need Help Implementing This in a Real Project?

Our team supports end-to-end development for web and mobile software, from architecture to launch.

jwt token securityjwt mistakesjwt authenticationjwt best practicestoken security

Found this helpful?

Join thousands of developers using our tools to write better code, faster.