JSON Web Tokens are not fragile by themselves. The fragile part is usually the trust boundary around them: which algorithm is accepted, which key is selected, and which claims are actually enforced by the API.
Algorithm confusion
The classic failure mode is a verifier that accepts whatever alg appears in the token header. If the server expects asymmetric RS256 but accepts symmetric HS256, the public key can become the HMAC secret.
import jwt
pub = open("public.pem").read()
payload = {"sub": "admin", "role": "superuser"}
# Sign with the public key as the HMAC secret.
forged = jwt.encode(payload, pub, algorithm="HS256")
print(forged)Never accept the algorithm from the token itself. Pin the expected algorithm server-side and reject everything else, including none.
The none algorithm
Some old or poorly configured libraries still treat {"alg":"none"} as a valid unsigned token. A safe implementation should reject unsigned tokens before claims are even evaluated.
kid header injection
The kid header tells the server which key to use. If the value is interpolated into a file path, URL, or SQL query, it becomes attacker-controlled input in the verification path.
const allowedKeys = new Map([
["prod-2026-01", process.env.JWT_PUBLIC_KEY_2026_01],
["prod-2026-06", process.env.JWT_PUBLIC_KEY_2026_06]
]);
export function resolveKey(kid: string) {
const key = allowedKeys.get(kid);
if (!key) throw new Error("unknown signing key");
return key;
}Treat kid like a user-supplied parameter. Use an allowlist, avoid path joins, and keep key rotation boring.
Hardening checklist
- Pin
algserver-side. - Validate
iss,aud,exp, andnbf. - Allowlist
kidvalues. - Prefer short-lived access tokens with rotation.
- Log verification failures without logging token contents.
Get these right and the entire exploit class shrinks dramatically. JWTs are not insecure; loose defaults are.
What to test in a real assessment
Start by collecting a normal token from a low-privilege account and decoding the header and claims without trusting them. Record the alg, kid, issuer, audience, expiry, and role-related claims. Then test whether the application validates those fields consistently across every API boundary. Authentication services are often strict, while downstream services quietly accept a weaker token.
Practical test cases:
- Change role-like claims and confirm the signature is rejected.
- Swap
audandissvalues between environments. - Reuse an expired token against background APIs and GraphQL endpoints.
- Check whether refresh tokens and access tokens are accepted interchangeably.
- Verify that logout, password reset, and account disable actions invalidate active sessions.
Detection and telemetry
JWT abuse often looks like normal API traffic unless you log verification failures and claim anomalies. Defenders should record token validation errors without storing full token values. Useful fields include token issuer, audience, algorithm, key id, expiry skew, user id, request path, source IP, and user agent.
Detection ideas:
- Alert on repeated
kidmisses from the same source. - Track requests with expired tokens that still reach business logic.
- Flag algorithm mismatch events and unknown issuers.
- Watch for low-privilege users suddenly accessing admin-only endpoints.
Secure implementation pattern
Keep token validation in one shared library or gateway policy. Every service should pin the allowed algorithm, issuer, audience, clock-skew tolerance, and key source. Avoid custom parsing in feature code, because that is where developers accidentally treat decoded claims as already trusted.
The most reliable JWT defense is boring consistency: one verifier, one policy, one key-rotation process, and tests that fail closed.
Key rotation without downtime
JWT systems often become risky because nobody wants to rotate signing keys. The safer pattern is overlapping keys with explicit key identifiers. Publish the new public key, begin signing new tokens with the new private key, keep the old public key available until old tokens expire, and then remove it. The verifier should accept only known kid values during the overlap window.
Rotation evidence is useful during audits and incidents. Record when a key was introduced, when signing moved, when the old key stopped validating tokens, and which services consumed the updated key set. If a key is suspected to be exposed, this evidence speeds up containment.
API design implications
Token claims should be minimal and stable. Avoid packing authorization decisions into long-lived tokens when the decision changes frequently. If a user's role, tenant access, or account status can change quickly, the API should check the current authorization state server-side or use very short-lived access tokens.
This is especially important in multi-tenant systems. A token with the wrong aud, stale tenant claim, or missing issuer validation can turn one environment's credential into another environment's access path. Strong validation keeps stateless authentication from becoming stateless trust.



