JWT Best Practices

Ivana KašikovićAug 8, 2025

Think Your JWTs Are Safe? Think Again.

In today’s digital world, where apps constantly exchange sensitive data, security is an important part of every application. Whether you're protecting authentication data or internal APIs, keeping data safe should be a top priority. These days, many apps rely on JSON Web Tokens (JWTs) for authentication. They're simple, efficient, and widely used for powering modern API flows and making stateless sessions fast and scalable.

But don’t let that simplicity fool you. Although JWTs might be easy to set up, a small mistake in implementation can lead to serious problems, from exposing user data to giving attackers unauthorized access. The impact of a badly handled token can cause huge damage.

Never Trust the Header Alone

One of the sneakiest attack vectors in JWTs hides in a place you might overlook, the header. This part of the token tells your app which algorithm was used to sign it. Sounds straightforward, but the problem is that attackers can inject their value and bypass your security.

ivana blog.jpg

Back in the past, researchers (with help from Auth0) discovered a clever attack. If your app expects tokens signed with RS256 (asymmetric), an attacker could modify the token header to say HS256 (symmetric) instead. Then, using your public key as the HMAC secret, they could forge a valid-looking token. Suddenly, they’re authenticated as anyone they want.

Even worse, some JWT libraries once accepted none value for alg, meaning no signature at allAttackers could send unsigned tokens, and the server would detect them as valid, that way they could completely bypass authentication.

To avoid this situation, never trust the algorithm declared in the token itself. Instead, enforce the expected algorithm on the server side, hardcode it or keep a strict whitelist (like only allowing RS256). Also, make sure you’re using solid JWT libraries that handle signature validation securely by default and make sure that you keep those libs up to date.

Use Secure Storage — Stop Leaving Tokens Lying Around

Storing JWTs in Local Storage or Session Storage might seem convenient, but it’s a dangerous trap. These storage locations are accessible to JavaScript, so if your site suffers from an XSS vulnerability, attackers can steal tokens and impersonate users. This combination of XSS and JWT theft is a recipe for disaster.

A safer way to keep tokens in is in secure, HTTP-only cookies. They are safe from XSS attacks since JavaScript cannot access them. Additionally, to lower the danger of CSRF, make sure to set the Secure and SameSite flags.

If you must use Local Storage (e.g., in SPAs or mobile apps) pay attention to:

  • enforce strict Content Security Policies,
  • sanitize all inputs,
  • use short-lived tokens,
  • pair access tokens with securely stored refresh tokens to maintain usability without compromising security.

Additionally, make sure to implement token expiration and revocation strategies. Use short-lived access tokens with refresh tokens and maintain token blacklisting to revoke leaked tokens. Also, have in mind to regularly rotate your signing keys using JWT’s kid header to keep things running smoothly.

Validate All Claims — Don’t Just Decode, Enforce

Don’t blindly trust JWT contents just because the signature’s valid. Just because a JWT's signature is valid doesn’t mean the token is safe to trust.

For secure apps it is crucial to check critical claims like:

  • exp (Expiration Time): without enforcing this, a hacker could reuse an expired token indefinitely.
  • nbf (Not Before): this ensures the token isn’t used before it’s supposed to be — an often-skipped check that can open timing-based attack windows.
  • iat (Issued At): useful for freshness checks and debugging, but can also be part of replay attack detection.
  • aud (Audience): crucial. If your app doesn’t validate that the token was intended for it, an attacker might trick your backend into accepting a token meant for another service.
  • scope: specifies the exact permissions the token holder has — defining what actions the client can perform on a protected resource on the user's behalf. It is a key part of permission control — without checking, you risk granting users more access than intended.

These aren’t always auto-validated by libraries. Also be careful how you handle decoded claims. If you merge JWT payloads into user objects, attackers can inject malicious nested fields like __proto__ to grant privileges through prototype pollution. That's the case especially in JavaScript environments.

Verifying the signature is only step one. To truly trust the token, you need to validate the full context — every claim, every time. Otherwise, you're giving attackers a signed pass with no expiration date or destination check.

Conclusion

All in all, JWTs are powerful tools, but you have to use them carefully. Their convenience and efficiency make them ideal for modern authentication, yet a single oversight can turn them into a dangerous weak point. So pay attention that you store them securely, validate properly, and never assume they’re safe just because they "look" valid.

Secure your tokens before they betray you.

References:

Share on: