JWT Security in 2026: Common Vulnerabilities and How to Test for Them
JSON Web Tokens (JWTs) are the default authentication mechanism for modern APIs. They're stateless, scalable, and well-supported by every framework. They're also frequently misconfigured in ways that let attackers forge tokens, escalate privileges, or bypass authentication entirely.
Here are the vulnerabilities that matter, how to test for them, and how to implement JWTs correctly.
JWT Anatomy (30-Second Refresher)
A JWT has three parts, separated by dots: header.payload.signature
- Header: specifies the algorithm (
{"alg": "HS256", "typ": "JWT"}) - Payload: contains claims — user ID, role, expiration (
{"sub": "user123", "role": "user", "exp": 1712534400}) - Signature: cryptographic signature that proves the token hasn't been tampered with
The header and payload are Base64URL-encoded (not encrypted — anyone can read them). The signature is what provides integrity. If you can forge the signature, you can forge the token.
Vulnerability 1: The "none" Algorithm
What it is: The JWT spec includes an "alg": "none" option that means "no signature required." If the server accepts tokens with alg: none, an attacker can create any token they want without knowing the secret.
How to test:
- Decode the JWT header
- Change
"alg"to"none"(also try"None","NONE","nOnE") - Remove the signature (everything after the second dot)
- Send the modified token
- If the server accepts it, the application is vulnerable
Fix: Never accept alg: none. Whitelist allowed algorithms explicitly in your JWT library configuration.
Vulnerability 2: Algorithm Confusion (RS256 → HS256)
What it is: RS256 uses asymmetric cryptography (public/private key pair). HS256 uses symmetric cryptography (shared secret). If the server uses RS256 but also accepts HS256, an attacker can:
- Get the server's public key (often available at
/.well-known/jwks.json) - Change the token's
algfromRS256toHS256 - Sign the token using the public key as the HS256 secret
- The server verifies the HS256 signature using the public key — and it matches
This works because the server uses the same key for verification regardless of algorithm. With RS256, it uses the public key to verify. With HS256, it uses the "secret" to verify — and the attacker set the "secret" to be the public key.
How to test:
- Fetch the public key from
/.well-known/jwks.jsonor the OIDC discovery endpoint - Change the token header to
"alg": "HS256" - Sign the modified token using the public key as the HMAC secret
- Send the token and check if it's accepted
Tool: jwt_tool automates this attack with the -X k flag.
Fix: Whitelist the expected algorithm. Never let the token's alg header determine which algorithm the server uses for verification.
Vulnerability 3: Weak Signing Secrets
What it is: HS256 tokens are signed with a shared secret. If the secret is weak (short, dictionary word, default value), it can be brute-forced.
Common weak secrets: secret, password, jwt_secret, changeme, the application name, empty string.
How to test:
- Capture a valid JWT
- Use
hashcatorjwt_toolto brute-force the secret - Hashcat mode 16500 handles JWT cracking:
hashcat -m 16500 jwt.txt wordlist.txt - If the secret is found, you can forge any token
Fix: Use a cryptographically random secret of at least 256 bits (32 bytes). Better yet, use RS256 with a proper key pair — there's no shared secret to brute-force.
Vulnerability 4: Missing Claim Validation
What it is: The server verifies the signature but doesn't validate the claims inside the token. An attacker can modify claims (user ID, role, permissions) and re-sign the token (if they know the secret or can exploit algorithm confusion).
How to test:
- Decode the payload and look for role/permission claims:
"role": "user","admin": false,"permissions": ["read"] - Modify them:
"role": "admin","admin": true,"permissions": ["read", "write", "delete"] - Re-sign the token (requires knowing the secret or exploiting another vulnerability)
- Check if the server grants elevated access
Also check:
sub(subject) — can you change the user ID to access another user's session?iss(issuer) — does the server validate who issued the token?aud(audience) — can a token for one service be used on another?
Fix: Validate all claims server-side. Don't trust the token's claims for authorization decisions without verification against the database.
Vulnerability 5: No Expiration or Long Expiration
What it is: Tokens without an exp (expiration) claim are valid forever. Tokens with very long expiration (days, weeks) remain valid long after the user's session should have ended.
How to test:
- Decode the token and check for
expclaim - If
expis missing, the token never expires - If
expis set, check how far in the future it is (>1 hour is usually too long for access tokens) - Test with an expired token — does the server reject it?
- Test after password change — is the old token still valid?
Fix: Always set exp. Access tokens should expire in 15-60 minutes. Use refresh tokens (with longer expiration) to get new access tokens. Implement token revocation for logout and password changes.
Vulnerability 6: JWK Injection
What it is: Some JWT libraries allow the token header to include a jwk (JSON Web Key) parameter containing the public key to use for verification. An attacker can generate their own key pair, sign the token with their private key, and embed their public key in the token header. The server uses the attacker's public key to verify — and it passes.
How to test:
- Generate a new RSA key pair
- Add the public key to the JWT header as a
jwkparameter - Sign the token with your private key
- Send the token
Tool: jwt_tool -X i automates JWK injection.
Fix: Never use the jwk header parameter for verification. Always use a server-side key store.
Testing Workflow
Here's the order to test JWT security:
- Decode: Paste the token into jwt.io to understand its structure
- Check expiration: Is
exppresent? Is it reasonable? - Test none algorithm: Quick check, high impact if vulnerable
- Test algorithm confusion: If the server uses RS256, try HS256 with the public key
- Brute-force secret: If HS256, try common secrets with hashcat
- Modify claims: If you can forge tokens, test privilege escalation
- Test revocation: Change password, logout — is the old token still valid?
Primary tool: jwt_tool by ticarpi — handles all of the above attacks with a single tool.
Secure Implementation Checklist
## JWT Security Checklist
- [ ] Algorithm whitelisted (never accept from token header)
- [ ] "none" algorithm rejected
- [ ] Secret is cryptographically random, ≥256 bits
- [ ] RS256 preferred over HS256 for public-facing APIs
- [ ] exp claim always set (15-60 min for access tokens)
- [ ] iss and aud claims validated
- [ ] sub claim verified against database
- [ ] Token revocation implemented (logout, password change)
- [ ] Refresh token rotation enabled
- [ ] JWK header parameter ignored
- [ ] Tokens stored in httpOnly cookies (not localStorage)
Bottom Line
JWT vulnerabilities are some of the highest-impact findings in API security testing. A single algorithm confusion or weak secret can give an attacker full admin access. The fixes are straightforward — whitelist algorithms, use strong secrets, validate claims, enforce expiration. Most JWT libraries support all of these out of the box; the vulnerabilities come from misconfiguration, not missing features.
For the full API security testing workflow, see our 10 API security checks. For the broader testing methodology, see the complete security testing checklist.