JWT: the implementation mistakes that expose your API
JSON Web Token has become the de facto standard for authentication in REST APIs. And with it, a whole family of vulnerabilities that automated scanners consistently miss, because they don't live in a line of code, but in implementation choices that seem harmless until someone takes a closer look.
What a JWT is, in one sentence
A JWT is a signed token in three dot-separated parts: a header (algorithm and type), a payload (the data, user_id, role, exp…), and a signature. The server issues the token after authentication, the client stores it and sends it back with every request. The server verifies the signature to ensure the payload hasn't been modified in transit.
This mechanism is solid in theory. In practice, implementation mistakes are common, widespread, and often critical, because the payload is only Base64-encoded, not encrypted. Anyone can read it. And if verification is done poorly, anyone can forge it.
Mistake 1 : The none algorithm
The JWT specification defines a special algorithm: none. A token signed with this algorithm has no signature. If the server accepts this token type, anyone can forge an arbitrary token: just modify the payload ("role": "admin") and declare "alg": "none" in the header. The signature is absent, the server accepts it.
This vulnerability was widespread in early versions of popular JWT libraries, some versions of jsonwebtoken (Node.js) or PyJWT (Python) were vulnerable by default. Recent versions fix the issue. But an outdated dependency, or an explicitly permissive configuration, is enough to reopen the door.
In a pentest, the check is immediate: modify the token header to set "alg": "none", remove the signature part, and replay the request. If the server responds 200, it is vulnerable.
Mistake 2 : Weak or predictable secret
When a token is signed with HMAC (HS256, HS384, HS512), security relies entirely on the strength of the secret. A weak secret, secret, password, changeme, the application name, the deployment date, can be broken by dictionary attack or brute force.
Tools like hashcat can test hundreds of millions of candidates per second. A single valid JWT token is enough to launch the attack: the attacker needs no special server access, just intercepting a token in transit, which happens naturally during a pentest.
A JWT secret must have at least 256 bits of entropy, randomly generated. Never a human-readable string.
Mistake 3 : RS256 / HS256 confusion
This is one of the most subtle JWT vulnerabilities. It appears when a server uses an asymmetric algorithm (RS256) but also accepts tokens signed with HMAC (HS256).
In RS256, the server signs with a private key and verifies with a public key. This public key is, by definition, accessible to everyone, sometimes exposed on a /.well-known/jwks.json endpoint. If the server also accepts HS256, an attacker can use this public key as an HMAC secret to sign a forged token. The library will use the public key to verify the HMAC signature, which was precisely created with that same key. Verification passes.
The fix: never accept multiple algorithms server-side. Explicitly set the expected algorithm in the verification configuration, and reject any token whose header declares a different algorithm.
Mistake 4 : Missing standard claims validation
Verifying the signature isn't enough. The standard payload claims must also be validated. A token can have a perfectly valid signature and still be dangerous if these checks are absent:
exp(expiration): an expired token must be rejected. Without this check, a stolen token remains valid indefinitely, even after a logout or password change.iss(issuer): validate that the token was issued by your server, not a third party using the same algorithm.aud(audience): in a microservices architecture, verify the token is intended for the right service. A token valid for service A must not be accepted by service B.
The takeaway
JWT vulnerabilities are not found with an automatic scanner. They require an understanding of the mechanism and a manual review of the implementation: which library, which version, which configuration, which algorithms accepted, which claims validated. This is precisely what an API pentest covers, not just the endpoints, but the authentication logic that protects them.
The simplest rule: use an actively maintained library, explicitly set the expected algorithm, systematically validate exp, iss, and aud, and generate secrets with a cryptographic generator, never by hand.
Further reading
A question after reading?
You use JWTs in your application and have doubts about your implementation? Send a message. No pitch, no automatic quote.
Or book a 30-minute scoping call directly
Book a call →