A JSON Web Token proves who a user is, but only if your server verifies it correctly. The most damaging JWT attacks — the alg=none bypass, RS256-to-HS256 key confusion, weak HMAC secret brute-forcing, and kid/jwk header injection — all exploit the same root cause: the server trusting attacker-controlled header fields instead of enforcing its own signing algorithm and key. Fix that one assumption and most JWT attack classes disappear.
For Indian API teams shipping fast on Node, Spring, or Django, JWTs are the default session mechanism — and misconfigured verification is one of the most common findings in API penetration tests today.
How JWT Attacks Work — and Where Trust Breaks
A JWT has three Base64URL-encoded parts separated by dots: header, payload, signature. The header declares the signing algorithm (alg) and, in many implementations, an optional kid (key ID) or jwk/jku (embedded or referenced public key). The payload carries claims like sub, exp, iss, aud. The signature is computed over the header and payload using a server-side secret or private key.
The critical design flaw many libraries inherited: the header is attacker-controlled. If your verification code reads alg from the incoming token and dynamically picks a verification method based on it, an attacker who can edit the header before you verify it effectively picks how you verify their forged token. That single mistake underlies almost every attack below.
Attack 1: The alg=none Bypass
The JWT spec defines none as a legitimate (unsecured) algorithm for cases where integrity is handled elsewhere. Some libraries, especially older or loosely configured ones, will accept a token with "alg": "none" and an empty signature as valid — because "no algorithm" technically means "no verification needed."
An attacker decodes a legitimate token, sets alg to none, edits the payload (e.g., "role": "admin"), removes the signature entirely, and re-encodes it. If the server's verify call does not explicitly reject none, the forged token is accepted.
Attack 2: RS256 to HS256 Key Confusion
This is the most consequential JWT vulnerability because it turns a public key into a forgery tool. RS256 is asymmetric: the server signs with a private key and verifies with a public key that can be shared freely (often exposed via a /jwks.json endpoint or embedded in a mobile app). HS256 is symmetric: the same secret both signs and verifies.
Some JWT libraries expose a single generic verify(token, key) function that does not enforce which algorithm the key is meant for. If an attacker knows the server uses RS256 and can obtain the public key, they can:
- Take the public key (freely available by design)
- Craft a token with
"alg": "HS256" - Sign it using the public key as the HMAC secret
- Send it to the server
jwt.verify(token, publicKey) without pinning algorithms: ['RS256'], it will use the attacker-chosen HS256 path with the public key as the HMAC secret — a secret the attacker already knows because it was never secret. The result: a validly "signed" token with any claims the attacker wants, including elevated privileges.
Know your vulnerabilities before attackers do
Run a free VAPT scan — takes 5 minutes, no signup required.
Book Your Free ScanAttack 3: Weak HMAC Secrets
When a team does use HS256 correctly, the security of every issued token still depends entirely on the strength of one shared secret. Weak secrets — short strings, dictionary words, default values left over from a tutorial or a leaked .env file — are brute-forceable offline once an attacker has a single valid token. Tools built for this (hashcat, John the Ripper JWT modules) can test millions of candidate secrets per second against the token's signature with no rate limiting possible, because the attack happens entirely offline.
Attack 4: kid Injection and SQL/Path Traversal via Header
The kid (Key ID) header tells the verifier which key to use when multiple signing keys are in rotation — useful, but dangerous if the server trusts it blindly. If the backend uses the kid value to build a file path (/keys/{kid}.pem) or a database lookup (SELECT key FROM keys WHERE id = '{kid}') without sanitizing it, an attacker can inject:
- Path traversal:
kid: ../../../../dev/nullto point the verifier at an empty or predictable file, which some libraries then treat as a zero-length key - SQL injection:
kid: ' OR '1'='1to manipulate the key-lookup query - Command injection: in rare cases where
kidreaches a shell call
Attack 5: jwk / jku Header Abuse
Some JWT implementations support an embedded public key (jwk) or a URL to a key set (jku) directly in the token header — intended for federated scenarios. If the server fetches and trusts whatever key the token itself supplies:
- With
jwk, the attacker embeds their own key pair in the header, signs the forged token with their own private key, and the server dutifully verifies it against the attacker-supplied public key. Signature check passes; the token is entirely attacker-controlled. - With
jku, the attacker hosts a malicious JWK Set on a server they control and points the header at it. If the server does not restrictjkuto an allow-listed, trusted domain, it will fetch and trust an attacker's keys.
Attack 6: Missing Expiry, Audience, and Issuer Validation
Even a correctly signed, unforgeable token can be misused if the server checks the signature but skips claim validation. Common gaps:
- No
expcheck — expired tokens (or tokens with no expiry at all) remain valid indefinitely, defeating logout and session rotation. - No
audcheck — a token issued for one service (e.g., a mobile app) is accepted by an unrelated internal service, enabling cross-service replay. - No
isscheck — a token from a different trust domain or environment (staging vs. production) is accepted as legitimate.
exp, aud, and iss checks. NIST's Digital Identity Guidelines (SP 800-63B) treat authenticator and session verification as a distinct control from identity proofing for exactly this reason — a valid signature only proves integrity, not that the session is still authorized.
Defence: Verifying JWTs Correctly
The fix for nearly every attack above is the same principle applied consistently: never let the token tell the server how to verify itself.
| Control | Why it matters | Implementation note |
|---|---|---|
| Pin the algorithm server-side | Stops alg=none and RS256-to-HS256 confusion | Pass algorithms: ['RS256'] explicitly to the verify call; never derive it from the token header |
| Use separate key material per algorithm | Prevents public keys doubling as HMAC secrets | Never let the same value serve as both an RSA public key and an HMAC secret |
| Strong, rotated HMAC secrets | Defeats offline brute-force | 256-bit minimum, generated with a CSPRNG, rotated on a schedule, unique per environment |
Validate kid against an allow-list | Stops path traversal / injection via header | Look up kid in a fixed in-memory map, never in a raw file path or unsanitized query |
Ignore or allow-list jwk/jku | Stops attacker-supplied keys | Reject embedded jwk outright unless required; restrict jku to a hardcoded trusted domain over HTTPS |
Enforce exp, aud, iss | Closes replay and cross-service misuse | Explicitly set audience, issuer, and clockTolerance options in the verify call |
| Short-lived access tokens + refresh flow | Limits blast radius of any forged or leaked token | Minutes, not days, for access tokens |
Testing Your Own Implementation
Before an attacker finds these issues, an API-focused penetration test should specifically attempt: forging an alg=none token, re-signing a captured token with the public key under HS256, brute-forcing the HMAC secret offline, injecting path-traversal or SQL payloads via kid, and submitting a self-signed jwk header. Automated API scanning combined with manual verification-logic review during a broader free VAPT scan catches this class of bug far more reliably than a code read-through, since the flaw is in runtime verification behaviour, not obviously wrong-looking code. Credential and token-based attacks remain the dominant initial access route into breached applications, per the Verizon Data Breach Investigations Report — which is exactly the trust boundary JWT verification is meant to protect.
alg confusion. Re-test verification logic after every auth-library change.Building This Into Your Development Process
For teams under India's DPDP Act 2023, an authentication bypass that exposes personal data isn't just a security incident — it is a reportable data breach with compliance consequences. Baking JWT verification checks into code review checklists, CI-based static analysis, and periodic penetration testing (ideally with a CERT-In empanelled partner for regulated engagements) closes the gap between "the library probably handles this" and "we verified it does." Bachao.AI, built by Dhisattva AI Pvt Ltd, runs automated and manual API testing designed to catch exactly this class of authentication-logic flaw before it ships. For broader regulatory context on why auth flaws matter beyond the technical fix, see the Bachao.AI blog and our guide on DPDP compliance.
Frequently Asked Questions
What is the alg=none JWT attack?
"alg": "none" with an empty signature as valid, letting an attacker forge any payload without knowing any secret. The fix is to explicitly reject none and pin the expected algorithm during verification.How does RS256 to HS256 key confusion work?
alg set to HS256. If the server's verify function doesn't enforce which algorithm the key belongs to, it accepts the forged token.Can a weak JWT secret really be brute-forced?
Why is the kid header dangerous?
kid value from the token header to build a file path or database query without sanitizing it, an attacker can inject path traversal or SQL payloads to redirect verification to a key of their choosing.What claims should every JWT verification check besides the signature?
exp), audience (aud), and issuer (iss). Signature validity alone only proves the token wasn't tampered with — it does not prove the token is still valid, intended for this service, or issued by a trusted source.