Back to blog
July 20, 2026

JWT Security Best Practices: How to Decode and Audit Tokens Safely

Learn the JWT security mistakes that actually get exploited — alg:none, algorithm confusion, missing expiry — and how to audit your own tokens safely without sending them to a server.

JSON Web Tokens (JWTs) are the backbone of authentication in most modern web apps. They're also one of the most commonly misconfigured pieces of a stack — and a misconfigured JWT isn't a minor bug, it's a way for an attacker to forge a valid login.

This guide covers the security mistakes that actually get exploited, and how to audit your own tokens for them.

First: A JWT Is Signed, Not Encrypted

The single most common misunderstanding about JWTs is that they're secret. They aren't.

A JWT has three parts separated by dots: header.payload.signature. The header and payload are just Base64-encoded JSON — not encrypted. Anyone who intercepts a token can decode the payload and read everything in it, no key required.

The signature only guarantees the token hasn't been tampered with. It does nothing to hide the contents.

Best practice: never put sensitive data in a JWT payload. No passwords, no full personal records, no secrets, no internal IDs you wouldn't want exposed. Treat the payload as public, because functionally, it is.

The "alg: none" Attack

Early JWT libraries — and some misconfigured modern ones — accept a token whose header specifies "alg": "none", meaning no signature at all.

An attacker takes a valid token, changes the algorithm in the header to none, strips off the signature, and forges whatever payload they want (say, "role": "admin"). If the server trusts the header and skips verification, it accepts the forged token.

Best practice: explicitly reject alg: none in your verification logic. Never let the token's own header dictate whether it gets verified. Configure your library to only accept the specific algorithm you actually use.

The Algorithm Confusion Attack (RS256 → HS256)

This one is subtler and more dangerous. It exploits the difference between symmetric and asymmetric signing.

  • RS256 is asymmetric: signed with a private key, verified with a public key.
  • HS256 is symmetric: the same secret both signs and verifies.

Here's the attack. Your server expects RS256 and verifies with its public key — which, being public, an attacker can obtain. The attacker changes the token's header from RS256 to HS256, then signs a forged token using your public key as the HMAC secret. If your server naively uses "the configured key" to verify without checking the algorithm, it will use the public key as an HMAC secret — and the forged token verifies successfully.

Best practice: pin the expected algorithm server-side. Your verification code should hard-code "verify this token as RS256" and reject anything else, rather than trusting the algorithm named in the header.

Missing or Excessive Expiration

A JWT without an exp (expiration) claim never expires. If it leaks, it's valid forever. On the other end, a token with an expiry set months out is nearly as bad — a stolen token stays useful for a long time.

Best practice: always set exp. Keep access-token lifetimes short (minutes to an hour) and use refresh tokens for longer sessions. Also set iat (issued-at) so you can reason about token age.

Missing Audience and Issuer Validation

The aud (audience) and iss (issuer) claims let you confirm a token was meant for your service and came from your auth server. Skipping these validations means a valid token issued for a different service might be accepted by yours.

Best practice: validate aud and iss on every request, not just the signature.

How to Audit Your Own Tokens

You can check for most of these issues by inspecting a token's structure:

  • Is the algorithm none? → critical.
  • Is it using a weak or unexpected algorithm? → investigate.
  • Is exp present, and is it reasonable? → check.
  • Does the payload contain anything sensitive it shouldn't? → check.
  • Are aud and iss present? → best practice.

Because a JWT's header and payload are just Base64, you can decode and inspect them without any secret key. But doing this by hand is error-prone — and pasting a production token into a random online decoder means sending it to a third-party server, which for a live token is its own security problem.

A safer approach is to audit tokens locally. FmtDev's JWT Security Auditor decodes and checks a token entirely in your browser — flagging alg: none, weak algorithms, missing expiry, sensitive payload data, and more — without the token ever leaving your machine. For decoding without the security checks, the JWT Decoder works the same client-side way.

The Bottom Line

JWTs aren't insecure by nature — they're insecure by misconfiguration. Pin your algorithm, reject none, set short expirations, validate aud and iss, and never put secrets in the payload. Then audit your real tokens to confirm you actually did all of that. Most JWT breaches come from skipping one of these steps and never checking.

Herramienta Asociada

¿Listo para usar la herramienta Decodificador JWT en Línea? Todo el procesamiento es local.

Abrir Decodificador JWT en Línea