JWT Security Best Practices: Risks to Know When Decoding Tokens
- The "none" algorithm vulnerability and how it works
- Algorithm confusion attacks (RS256 vs HS256 confusion)
- Why short expiry times matter and how to set them
- What server-side validation must check beyond signature
Table of Contents
Decoding a JWT shows you what it claims — but decoding is not validation. A malicious user can create a JWT with any payload they want if your server does not properly verify it. These are the JWT security issues every developer should know.
The "alg: none" Vulnerability
The JWT spec originally allowed "alg": "none" in the header to indicate an unsigned token. Some early JWT libraries would accept these tokens as valid, meaning an attacker could forge a token with any payload by setting the algorithm to "none" and providing an empty signature.
When you decode a JWT, check the header's alg field. If you ever see "alg": "none" in the wild:
- Your server library should reject it immediately (any modern library does by default)
- If you are building a JWT validator, explicitly reject the "none" algorithm
- Never allow the client to specify which algorithm to use for validation
Modern JWT libraries like jsonwebtoken (Node.js) and PyJWT explicitly reject "none" by default. But always check your library's configuration.
RS256 vs HS256 Confusion Attack
This is a subtle but dangerous attack. RS256 uses a public/private key pair. HS256 uses a shared secret. The attack works like this:
- A server uses RS256 and publishes its public key (normal behavior)
- An attacker takes the public key and creates a new JWT signed with HS256, using the public key as the HS256 secret
- If the server auto-detects the algorithm from the token header, it verifies the HS256 signature using the public key — and it succeeds
The fix: never trust the algorithm specified in the JWT header. Your server should have a hard-coded expected algorithm and reject tokens using any other algorithm.
// Node.js - SECURE: specify expected algorithm explicitly
jwt.verify(token, publicKey, { algorithms: ['RS256'] })
// NOT secure: allowing the token to dictate algorithm
jwt.verify(token, publicKey) // some older library versions
Sell Custom Apparel — We Handle Printing & Free Shipping
What Your Server Must Validate Beyond the Signature
After verifying the signature, a secure JWT validator should check:
- exp (Expiration): Reject tokens past their expiry. Use short expiry times — 15 minutes for access tokens is common. Refresh tokens can be longer-lived.
- iss (Issuer): Verify the token was issued by your expected identity provider. Reject tokens from unknown issuers.
- aud (Audience): Verify the token was issued for your specific API. An access token for Service A should not be accepted by Service B.
- nbf (Not Before): If present, reject tokens used before this time.
- jti (JWT ID): For high-security flows, track used JTI values to prevent token replay attacks.
Access Token Expiry: How Short Is Short Enough?
JWTs are stateless — once issued, they cannot be revoked without extra infrastructure. This makes expiry time a key security lever:
- Access tokens: 5–15 minutes is a common best practice. Short enough that a stolen token has a small window of usefulness.
- Refresh tokens: 1–30 days, stored securely (httpOnly cookie). Used to get new access tokens without re-authentication.
- ID tokens (OIDC): Usually 1 hour — used for session display, not API authorization.
If you need true revocation (logout everywhere), you need either a token blocklist (Redis is common) or very short expiry times. There is no native revocation in the JWT spec.
Inspect Your JWT Claims and Algorithm
Paste your token above to see the algorithm, claims, and expiry — useful first step in any security review.
Open Free JWT DecoderFrequently Asked Questions
Is decoding a JWT a security risk?
Decoding itself is not a risk — it is just base64 decoding. The risk is trusting decoded claims without verifying the signature, or putting sensitive data in the payload that should not be readable by clients.
What should never be put in a JWT payload?
Passwords, full credit card numbers, SSNs, or any data you would not want readable by anyone who has the token. JWT payloads are base64-encoded, not encrypted — anyone with the token can decode and read them.
How do I revoke a JWT before it expires?
JWTs are stateless by design, so revocation requires a server-side blocklist of revoked JTI values, short expiry times, or a stateful reference token pattern (opaque tokens that look up a JWT server-side).

