Treat every JWT header field as untrusted input. RFC 8725 says to sanitize the kid value to prevent injection, restrict jku and x5u URLs to an allowlist, and not send cookies when fetching them. Decide the algorithm and key from your own configuration, not from the token.
Why headers are dangerous
A JWT header is read before the signature is checked, because you need it to find the key. That means an attacker chooses it. Three fields cause most trouble.
- kid names which key to use. If your code builds a file path or database query from it, injection follows.
- jku is a URL to a set of keys. If you fetch it blindly, the attacker supplies their own key set and signs the token with it.
- x5u is a URL to a certificate chain, with the same problem.
The same document's guidance on the alg header also applies: libraries should let callers specify allowed algorithms and reject all others.
A validation order that works
- Parse the token and reject anything malformed.
- Look up the allowed algorithms for this issuer in your config. Reject if the alg header is not on the list.
- Resolve the key from a trusted source: a key set you configured for this issuer, fetched from a URL you chose, not the one in the header.
- If you use kid, treat it as an opaque lookup key. Match against known identifiers with an exact comparison. Do not concatenate it into paths or queries.
- Verify the signature. Reject the whole token if it fails.
- Only then check claims.
Handling jku and x5u
The safest answer is to ignore them. If you must honour them, RFC 8725 says to restrict to an allowlist of URLs, and not to send cookies on the fetch. Add the usual SSRF defences: resolve the host yourself, block private and metadata addresses, set short timeouts and cap the response size.
Key binding
RFC 8725 also recommends that each key be bound to exactly one algorithm. A verifier should not accept an HS256 token signed with what is really an RSA public key. That is the key confusion attack, and per-key algorithm binding closes it.
Test cases to write
- kid containing path traversal characters or SQL fragments.
- jku pointing to an attacker host or an internal address.
- Token signed with a valid key but a different algorithm than the one configured.
- Header says none.
- Expired token with a good signature.
Quick check
Can I trust kid if the signature verifies?
The kid is read before verification, so injection can happen before you know the signature is good. Sanitize it at the point of use.
Do I ever need jku?
Usually not. Configure the issuer's key set URL on your side. If a specification requires jku support, use a strict allowlist.
Safeguard's dependency scanning can flag which JWT libraries and versions your projects use, and you can start with the free scan at gold.safeguard.sh/scan.
Sources
Last reviewed: 19 October 2026.