JSON Web Tokens (JWTs) are used to authenticate requests in the vast majority of modern REST APIs. When implemented correctly, they provide a stateless, scalable authentication mechanism. When implemented incorrectly — which happens more often than most teams realize — they become a direct path to account takeover, privilege escalation, and unauthorized data access. This guide covers the most impactful JWT vulnerabilities, how to test for each one, and what correct implementation looks like.
This article was created with AI assistance and reviewed by the DastPilot team.
A quick JWT primer
A JWT consists of three base64url-encoded parts separated by dots: a header, a payload, and a signature.
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0Iiwicm9sZSI6ImN1c3RvbWVyIn0.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
The header declares the signing algorithm. The payload contains claims — user ID, role, expiry time, and any other data the issuer includes. The signature is a cryptographic proof that the header and payload have not been tampered with.
The security of a JWT depends entirely on the server correctly validating the signature before trusting the claims. Every JWT vulnerability is, at its core, a failure of that validation.
Vulnerability 1: The "none" algorithm attack
The JWT specification includes an algorithm value of none, which means the token is unsigned. Some JWT libraries, when they encounter a token with alg: none in the header, skip signature verification entirely and accept the token as valid.
How to test for it
- Obtain a valid JWT from the API.
- Decode the header and change the
algfield tonone(also tryNone,NONE, andnOnE— some libraries are case-sensitive). - Remove the signature portion of the token (the third segment), leaving a trailing dot:
header.payload. - Send the modified token to a protected endpoint.
- If the API returns a successful response, the none algorithm attack is confirmed.
What correct behavior looks like
The API should return 401 Unauthorized for any token with alg: none. The server should explicitly whitelist the algorithms it accepts and reject any token that uses an algorithm outside that list.
Vulnerability 2: Algorithm confusion (RS256 to HS256)
This is one of the most subtle and dangerous JWT vulnerabilities. It exploits the difference between asymmetric algorithms (RS256, ES256) and symmetric algorithms (HS256).
When an API uses RS256, it signs tokens with a private key and verifies them with a public key. The public key is, by definition, public — it may be exposed at a JWKS endpoint or embedded in documentation. If the server's JWT library accepts both RS256 and HS256, an attacker can take the public key, use it as the HMAC secret to sign a forged token with alg: HS256, and the server will verify it successfully — because it is using the public key as the HMAC secret, which is exactly what the attacker used to sign the token.
How to test for it
- Identify whether the API uses RS256 or ES256 (check the token header or the JWKS endpoint).
- Obtain the server's public key from the JWKS endpoint (
/.well-known/jwks.jsonor similar). - Forge a new JWT with modified claims (e.g., elevated role), sign it with HS256 using the public key as the secret.
- Send the forged token to a protected endpoint.
- If the API accepts it, algorithm confusion is confirmed.
What correct behavior looks like
The server should explicitly specify which algorithm to use for verification — not infer it from the token header. The JWT library should be configured with a fixed algorithm, not a list that includes both asymmetric and symmetric options.
Vulnerability 3: Weak HMAC secrets
HS256-signed JWTs are only as secure as the secret used to sign them. If the secret is short, predictable, or a common default value, it can be brute-forced offline using the token itself — no server interaction required.
How to test for it
- Obtain a valid JWT from the API.
- Run it through a JWT cracking tool (such as
hashcatwith mode 16500 orjwt-cracker) against a wordlist of common secrets. - Common weak secrets to try:
secret,password,jwt_secret,changeme, the application name, and any strings found in public repositories for the application. - If the secret is cracked, forge a new token with elevated claims and test it against the API.
What correct behavior looks like
HMAC secrets should be at least 256 bits of cryptographically random data. They should never be hardcoded in source code, committed to version control, or derived from predictable values. Use a secrets manager and rotate secrets on a defined schedule.
Vulnerability 4: Expired token acceptance
JWTs include an exp (expiry) claim that specifies when the token should no longer be accepted. If the server does not validate this claim — or validates it with an excessively large clock skew tolerance — expired tokens remain valid indefinitely.
How to test for it
- Obtain a valid JWT and note its expiry time from the
expclaim. - Wait for the token to expire (or manually set the system clock forward if testing in a controlled environment).
- Send the expired token to a protected endpoint.
- If the API returns a successful response, expired token acceptance is confirmed.
- Also test with a token where the
expclaim has been removed entirely — some libraries skip expiry validation when the claim is absent.
What correct behavior looks like
The server should validate the exp claim on every request and return 401 for expired tokens. Clock skew tolerance should be minimal — typically no more than 60 seconds. Tokens with no exp claim should be rejected unless the application explicitly requires non-expiring tokens for a specific use case.
Vulnerability 5: Claim tampering without signature verification
Some APIs extract claims from the JWT payload without verifying the signature first. This allows an attacker to modify any claim — user ID, role, permissions — by simply re-encoding the payload with different values and submitting the token with the original (now invalid) signature.
How to test for it
- Obtain a valid JWT.
- Decode the payload and modify a claim — change the
rolefromcustomertoadmin, or change thesubto another user's ID. - Re-encode the modified payload without changing the signature.
- Send the tampered token to a protected endpoint.
- If the API accepts the token and acts on the modified claims, signature verification is not being performed correctly.
What correct behavior looks like
Signature verification must happen before any claims are read or acted upon. The verification and claim extraction steps should never be separated in the code path. Use a well-maintained JWT library that handles this correctly by default rather than implementing JWT parsing manually.
Vulnerability 6: Missing token invalidation on logout
JWTs are stateless by design — the server does not store them, so there is no server-side session to invalidate. If an application does not implement a token blocklist or short expiry windows, a token remains valid after logout until it naturally expires. This means a stolen token can be used even after the legitimate user has logged out.
How to test for it
- Authenticate and obtain a JWT.
- Call the logout endpoint.
- Immediately use the pre-logout token to access a protected endpoint.
- If the API returns a successful response, token invalidation on logout is not implemented.
What correct behavior looks like
For applications where immediate invalidation is required (most applications), implement a token blocklist using a fast store like Redis. Store the JTI (JWT ID) claim of invalidated tokens and check against the blocklist on every request. Alternatively, use very short-lived access tokens (5–15 minutes) combined with refresh tokens, so the blast radius of a stolen token is limited by time.
Automating JWT security testing
Manual JWT testing is effective for targeted assessments, but it does not scale across many APIs or frequent release cycles. DastPilot automates the most impactful JWT checks as part of its standard API DAST scan:
- None algorithm attack across all protected endpoints
- Expired token acceptance testing with configurable expiry windows
- Missing token validation detection (endpoints that accept requests without any token)
- Claim tampering detection for role and identity claims
- Auth token refresh handling during long scan runs to maintain valid session state
These checks run automatically on every scan, against every endpoint in your OpenAPI spec, without requiring manual test case construction for each new endpoint you add.
JWT vulnerability testing summary
| Vulnerability | Test method | Expected secure response |
|---|---|---|
| None algorithm attack | Set alg: none, remove signature |
401 Unauthorized |
| Algorithm confusion (RS256 → HS256) | Sign with public key as HMAC secret | 401 Unauthorized |
| Weak HMAC secret | Offline brute-force with wordlist | Secret not crackable; 401 for forged tokens |
| Expired token acceptance | Submit token past exp time |
401 Unauthorized |
| Claim tampering | Modify payload, keep original signature | 401 Unauthorized |
| Missing logout invalidation | Use pre-logout token after logout call | 401 Unauthorized |
Final takeaway
JWT vulnerabilities are not theoretical — they appear regularly in production APIs, often in code that looks correct at a glance. The none algorithm attack and algorithm confusion vulnerabilities in particular have caused real breaches in widely used applications.
The good news is that all of these vulnerabilities are testable, and most are straightforward to fix once identified. The key is making JWT security testing a regular part of your API security program — not a one-time check during an annual audit.
Automate JWT security testing across your APIs
DastPilot tests for JWT vulnerabilities on every scan run — none algorithm attacks, expired token acceptance, claim tampering, and more.
Start Free Trial