JWT vulnerabilities: how they work, how to detect them, how to fix them

Weak JSON Web Token handling - alg:none, no expiry, or a manipulable kid header - lets attackers forge or replay tokens and impersonate users.

Typical severity: High · Checked by NovaPentest module: JWT Security

What it is

JSON Web Tokens (JWT, RFC 7519) are compact, self-contained tokens widely used for authentication and session state. A JWT has three base64url parts - header, payload, and signature - joined by dots. The header names the signing algorithm; the payload holds claims such as the subject, roles, and an expiry (exp); the signature lets the server verify the token was not altered. The security of the whole scheme rests on the server verifying that signature correctly and trusting only what it can cryptographically confirm.

Vulnerabilities appear when that verification is weak or optional. The classic flaw is accepting alg:none, where a token declares it has no signature and a permissive library treats it as valid - letting anyone rewrite the payload. Related issues include trusting the algorithm named in the token (enabling algorithm-confusion attacks), resolving the signing key from an attacker-influenced kid header, tokens with no exp claim so they never expire, tokens with an excessively long lifetime that widen the replay window, and transmitting tokens over cleartext HTTP where they can be sniffed.

Because a JWT often is the session, forging or stealing one is equivalent to logging in as the victim - and if roles are encoded as claims, escalating to administrator. These map to OWASP Cryptographic Failures and Authentication Failures and to CWE-347 (improper signature verification) and CWE-613 (insufficient session expiration).

How it is exploited

The highest-impact JWT attack targets signature verification. In the alg:none variant, an attacker takes a legitimate token, edits the payload - for example flipping "role":"user" to "role":"admin" or changing the subject - sets the header algorithm to "none", and removes the signature entirely. A server that honors the header's algorithm accepts the unsigned token as authentic, granting whatever the forged claims request.

Algorithm confusion is a cousin: a service that verifies RS256 with a public key can be tricked into verifying an HS256 token where that public key is used as the HMAC secret, letting the attacker sign tokens themselves. A manipulable kid (key ID) header is another route - if the server uses kid as a file path or URL to load the verification key, an attacker can point it at a predictable file or a server they control and sign tokens with a known key.

Tokens lacking an exp claim never expire, so a single stolen token (via XSS, logs, a referrer leak, or a shared link) works forever; overly long expiries have the same effect on a smaller scale. Tokens sent over HTTP can simply be captured on the network and replayed. In every case the payoff is the same: authenticate as another user without their credentials, and where claims carry privileges, escalate to admin.

How to detect it

NovaPentest's JWT Security module fetches the target URL and looks for tokens with the JWT shape in the response headers, Set-Cookie headers and body. If a token is sent over plain HTTP, it reports cleartext transmission as Critical.

For up to three tokens it decodes the header and payload and checks them: a header declaring the algorithm "none" is reported as Critical; a kid value that looks like a file path or a URL is reported as High; a payload with no exp claim is reported as High; and an expiry more than 24 hours away is reported as Medium.

It also checks, with a differential test, whether the server enforces signatures. It sends the same token twice, once with its signature deliberately corrupted and once with the signature removed and the algorithm set to "none". It reports a Critical finding only when the server rejects the corrupted signature with 401 or 403 but accepts the unsigned token. A page that returns 200 for any token is not reported, because that page is not checking tokens in the first place.

The module only inspects tokens it can see in the response to the target URL. It does not log in, so tokens issued only after authentication, and APIs that are not the scanned URL, are outside what it can test.

To verify manually, decode each segment of a token (a JWT debugger works, as does `base64 -d` on each part) and read alg, kid and exp. Confirm the endpoint uses HTTPS. Then, on an endpoint that requires authentication, check that a token with a modified payload or a missing signature is rejected with 401.

How to fix it

1. Pin the algorithm server-side. Configure your JWT library with an explicit allowlist of accepted algorithms (for example HS256 or RS256) and reject anything else - never derive the algorithm from the token header, and always reject alg:none.

2. Verify every signature and fail closed. A token that does not verify, or whose algorithm is not on the allowlist, must be rejected, not treated as unsigned-but-valid.

3. Do not let the token choose its key. Resolve signing keys from server-side configuration; if you use kid, validate it against a strict allowlist of known key identifiers and never use it as a file path or URL.

4. Always set a short exp. Use short-lived access tokens (roughly 15-60 minutes) plus refresh-token rotation; add nbf/iat and validate them.

5. Provide revocation. Keep a denylist or a per-user token version/generation claim so stolen tokens can be invalidated before they expire.

6. Protect tokens in transit and at rest. Serve every token-bearing endpoint over HTTPS/HSTS; for cookie-based JWTs set Secure, HttpOnly, and SameSite. Keep tokens out of URLs, logs, and referrers.

7. Validate audience and issuer (aud/iss) so a token minted for one service cannot be replayed against another.

Prioritize alg:none and cleartext HTTP first (full forgery/interception), then missing expiry and kid handling, then long-lived tokens.

Check your own site

Start with the free passive scan: no account needed, and you get an A to F grade covering headers, TLS, cookies, DNS and other configuration a visitor can observe. It does not send attack payloads, so it will not confirm every class of flaw. The full scan, available with a free account, runs the active checks, including the one described on this page.

Scan your site free