flashman
← All guides

Validate JWT crit header extensions

Accept or reject JWT crit extensions deliberately using protected-header rules, unknown names, and the 401 that is not a bad signature or expired exp.

2026-10-01 · 8 min read

  • jwt
  • jose
  • security

The JOSE crit header tells a verifier that certain extension headers are mandatory. RFC 7515 requires the verifier to understand every listed name and to reject the JWS when it does not. A library that skips unknown headers will accept a token whose meaning it did not implement.

crit is optional. Producers should omit it unless an extension truly changes how the recipient must process the token. Adding it by default turns every new verifier into an outage.

Keep crit inside the protected header

In a compact JWS the protected header is the first Base64url segment. crit must appear there so the signature covers it. A crit value glued on after verification, or placed only in an unprotected header, does not meet the requirement.

The value is an array of header names. Each name must occur in that header. The array must not be empty, must not contain duplicates, and must not name parameters already defined for JWS or JWA, such as alg, kid, or typ.

  • Parse the protected header before verifying, and fail closed on malformed JSON.
  • Reject an empty crit array.
  • Reject a listed name that is missing from the header.
  • Reject a listed name the service does not implement.

Implement the extension or do not accept the token

Understanding a name means applying its rules, not merely recognizing the string. If crit lists an extension that changes the payload encoding or the claim set, the verifier has to perform that transformation before it trusts the claims.

When two services disagree, compare their supported lists. A gateway that ignores crit and an API that enforces it will produce a 401 that the gateway's access logs never explain. Pick one enforcement point and make the other pass the protected header through unchanged.

Classify the 401 before rotating keys

A crit failure is a header-contract failure. It can happen on a token whose signature would verify and whose exp is still in the future. Key rotation, clock skew, and audience mismatches are separate checks that should be logged with their own reason.

Ask the producer why the extension is required. If it is not required, stop sending crit. If it is required, ship the verifier support first and only then send the header in production.

Use fixtures instead of production tokens

Create synthetic tokens: no crit, crit with one supported name, crit with one unknown name, an empty array, a standard name such as alg inside crit, and a listed name that is absent from the header. Only the first two should pass a verifier that implements that one extension.

Keep the signing key in the test fixture. A valid signature on the unknown-name token is the point of the test: the header must still be rejected.

Inspect the header locally

Use Flashman's JWT decoder to read the protected header, the Base64 tool on the first segment, the JSON formatter for the header object, and diff to compare the token a producer emits with the token the verifier accepts.

Record the rejected crit name in the incident ticket. The next reader should not have to guess whether the outage was a key, an expiry, or an extension.

Try these tools