Last updated: April 29, 2026
JSON Web Tokens are the de-facto authentication primitive for modern APIs. Most JWT implementations are correct. The ones that are not have systematic, well-known weaknesses — algorithm confusion, weak signing keys, key confusion, kid injection — that turn the entire authentication layer into trivial spoofing. This article covers the attack surface, the modern variants, the detection that catches exploitation, and the implementation rules that close every category.
Why JWT bugs persist
JWT was specified loosely. The header includes the algorithm, which the verifier reads — meaning the server can be told which algorithm to use to verify the token. Combined with libraries that historically supported “none” as an algorithm, this gave us the original “alg: none” bypass. That specific bug is mostly fixed in modern libraries, but the pattern continues:
- RS256 vs HS256 confusion still works in poorly written verifiers
- JWKS endpoints with weak access controls let attackers inject their own keys
- Weak HMAC secrets remain crackable offline
kid(Key ID) headers without sanitisation lead to SQL/path injection in some implementations
The attacks that still work
1. alg: none
Set the alg header to none, omit the signature, hope the verifier accepts:
{
"alg": "none",
"typ": "JWT"
}.{
"sub": "admin",
"exp": 99999999999
}.
Defunct in modern libraries (jsonwebtoken v9+, pyjwt v2+). Still works on hand-rolled or legacy implementations. Always test it.
2. RS256 to HS256 confusion
The classic. RS256 uses a public/private key pair; HS256 uses a shared secret. If the server is configured for RS256 and the verifier accepts the algorithm from the header, you can:
- Take the server’s public key (often exposed at
/.well-known/jwks.json) - Forge a token with
alg: HS256in the header - Sign it using the public key as the HMAC secret
- The server, expecting RS256 to verify with its public key, instead uses it as an HMAC secret because the header said HS256
- Validation succeeds
Tools: jwt_tool, Burp’s JWT Editor extension automate this.
3. Weak HMAC secret
HS256 uses a shared secret. If the secret is short or guessable (we routinely find secret, jwt-secret, changeme, environment names plus a number), it is crackable offline:
hashcat -a 0 -m 16500 token.txt rockyou.txt
Mode 16500 is JWT HMAC. Cracks in seconds for weak secrets.
4. JWKS injection
Newer pattern. The kid (Key ID) header points to a key. If the server fetches the key from a URL embedded in the token (the jku header), the attacker can host their own JWKS endpoint and sign with the corresponding private key.
{
"alg": "RS256",
"typ": "JWT",
"kid": "1",
"jku": "https://attacker.com/jwks.json"
}
The server fetches https://attacker.com/jwks.json, finds key with kid: 1, verifies the signature against it. Verification succeeds because the attacker controls the key.
Mitigation: validate the jku URL against an allow-list, or never trust jku from the token.
5. kid injection (path traversal / SQLi)
Some servers use the kid value to look up the signing key — from a file path, a database query, or a command. If kid is concatenated unsanitised:
{ "kid": "../../../dev/null" }
// Server reads /dev/null as the key, which is empty.
// Attacker signs with empty string. Verifies.
{ "kid": "x' UNION SELECT 'attacker_secret' -- " }
// SQL injection in kid lookup; returns attacker-controlled secret.
6. Algorithm downgrade
Force the server to a weaker algorithm. ES256 down to HS256, etc. Effective when the server’s algorithm-validation logic is permissive.
7. Token signature stripping
Send the token without a signature, or with the signature replaced by garbage. If the server logs the token but does not enforce signature validation in some code path, exploitation follows.
How to test JWT systematically
- Capture a valid token. Login as a regular user, capture the JWT.
- Decode it. Burp’s JWT Editor or
jwt-cli. - Modify claims and resend. Change
subto admin, changeroleto admin. If accepted, signature is not being validated. - Test alg: none. Strip signature, set
alg: none. - Test RS256-to-HS256. Find the public key (JWKS endpoint), forge with HS256 using the public key.
- Crack HMAC secret offline. Use hashcat with rockyou + best64 rules.
- Test jku / x5u injection by adding the headers and pointing at attacker-controlled JWKS.
- Test kid injection with path traversal and SQL injection payloads.
Implementation rules that close every variant
- Hardcode the expected algorithm. Do not read
algfrom the token header. Tell the verifier explicitly: “expect RS256.” Most modern libraries support this; use it. - Use a strong signing secret. For HS256, at least 256 bits of cryptographic randomness. Generate via
openssl rand -hex 32or equivalent. Store in a secret manager. - Disable the
nonealgorithm at the library level even though modern defaults block it. Defence in depth. - Reject
jku,jwk,x5u,x5cheaders from the client unless your protocol specifically requires them and you allow-list URLs. - Sanitise
kidagainst expected key identifiers. Never use it as a path or SQL parameter. - Short token lifetime. Access tokens 5-15 minutes. Refresh tokens with rotation.
- Token revocation — maintain a revocation list (Redis with TTL, or short-lifetime + opaque session ID).
- Validate
iss,aud,nbf,expclaims explicitly on every verification.
Detection — what works at runtime
- WAF rules for malformed JWTs in Authorization headers — empty signatures,
alg: noneheader values. - Application logs for token validation failures. Sustained failures from a single IP indicate enumeration / cracking attempts.
- Outlier detection on token usage — same token from multiple IPs in different geographies within seconds is anomalous.
- JWT inspection at the gateway (API Gateway, Kong, Apigee) — log every token’s claims, alert on impossible claim combinations.
How to find your next JWT bug
For attackers:
- Test every API that uses bearer tokens — not just the obvious user-facing app, but admin APIs, microservice-to-microservice tokens, OAuth resource servers.
- Look for JWKS endpoints exposed publicly. If yes, RS256-to-HS256 is your first test.
- Burp Pro’s JWT Editor +
jwt_tooltogether cover ~95% of JWT attack vectors. - For mobile-app APIs, decompile the app and look for hard-coded HMAC secrets — surprisingly common.
For defenders:
- Audit every JWT consumer in your codebase. For each, what library, what algorithm enforcement, what secret source?
- Run
jwt_toolagainst your own production tokens with various tampers and confirm verification fails on all. - Migrate from HS256 to RS256 for any token that crosses trust boundaries.
Compliance angle
- OWASP API Security Top 10 — broken authentication is the #1 category.
- DPDP §8(5) — JWT bypass that lets attackers access personal data is a reasonable-security failure.
- RBI / SEBI — authentication weaknesses in customer-facing APIs are reportable findings.
The takeaway
JWT done right is fine. JWT done with library defaults from 2017 is a privilege-escalation primitive. The implementation rules above are not novel; they have been published by every JWT library maintainer. Apply them to your next API design review. Test your existing implementations with jwt_tool before someone else does. JWT is one of those technologies where the difference between secure and broken is small in lines of code and large in business consequence.
Try it: hands-on challenge
Get a VAPT scoping call
Senior practitioner-led VAPT — not a checklist run by juniors. CVSS-scored findings, free retest, attestation letter. India's SMBs and SaaS teams.