Skip to content
Keys From the Token Itself: jku, jwk, x5u, x5c
step 1/5

Reading — step 1 of 5

Learn

~4 min readSecurity & Operations

Keys From the Token Itself: jku, jwk, x5u, x5c

The registry you just built answers one question: which of my keys is this? kid is a name, resolved against a key set you fetched from an issuer you chose. Four other header parameters answer a different question — which key should I use? — and let the token answer it.

Four parameters, one idea

The JWS specification (RFC 7515) defines these alongside kid:

ParameterHeader carriesA trusting verifier
jwka public key, inlineverifies with it
jkua URL of a JWK Setfetches the set, uses a key from it
x5ca certificate chain, inlinetakes the key from the leaf
x5ua URL of a certificate chainfetches it, then as above

kid is a name; these four are the key itself, or a pointer to it. That is the difference between a lookup and an instruction.

python

Why this can never be made secure

RFC 7519 §11.1 settles it: a JWT's contents cannot be relied on for a trust decision unless they are cryptographically secured and the signing key is verifiably under the control of the party named as the issuer. A key that arrived inside the token is under the control of whoever wrote the token. An attacker facing a verifier that honours jwk therefore needs no secret and no weakness in HMAC or RSA: they generate a key pair, put the public half in the header, sign with the private half, and the check passes — proving only that the sender can sign with their own key.

python

Where algorithm confusion had the token choosing what to do with your key, this is the token choosing which key — the same failure one level up.

The two URL forms cost you a request as well. You must fetch the key before any signature is verified, so an unauthenticated stranger picks a URL your backend retrieves: an internal admin page, a cloud metadata endpoint, one host of a private range at a time. Rejecting the key afterwards does not un-send that.

Defenses that only look like defenses

  • Allowlisting the jku host. One open redirect there, or one endpoint that echoes attacker JSON back, and the allowlist is satisfied.
  • Accepting jwk when you recognise its thumbprint. That is kid with a key parser bolted on: same lookup, more attack surface, no new capability.
  • Validating the x5c chain. Against which root? If it is a CA you operate, you have just described a key store you control — so use the store.

The clause behind the rule

Resolve keys by name, from a store you control. RFC 7519 §7.2 lets you be blunt: step 5 requires the JOSE header to hold only parameters whose syntax and semantics your implementation understands and supports, bar any the spec marks ignorable when not understood, and a failed step means the JWT MUST be rejected. typ is the one this spec says implementations ignore (§5.1). A jku you do not implement gets no such exemption from you: treat it as invalid input, not as a hint.

Common mistakes

  • Falling back to the header key when no other key matches. The unknown-kid path is where this gets written, and it turns a miss into a bypass.
  • Denylisting header parameters instead of allowlisting them. New ones get registered; your list of four does not grow by itself.
  • Fetching first and judging afterwards. The request has already left.
  • Allowing jku because it points at your own JWKS host. It still moves the choice of key into the token.

Your exercise

You will accept kid lookups against a trusted registry and reject any token whose header carries key material or a key URL.

The visible test punishes scanning for the wrong thing. Its third token carries kid: k1 and a signature that is a correct HMAC under k1, so everything about it verifies. It also carries a jku, so the answer is REJECTED header_key=jku. Scan for the four before alg and before the registry, or you report OK kid=k1 on a token that asked you to fetch a key from keys.attacker.test.

With several of the four present, report the first in the fixed order jwk, jku, x5c, x5u, not whichever came first in the JSON. And a token with no kid is REJECTED unknown_kid: it is well formed, you simply hold no key for it, and trying your other keys is the very fallback this lesson is about.

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…

Keys From the Token Itself: jku, jwk, x5u, x5c — Build a JWT Library