PrefectHQ/fastmcp · error · ValueError

No matching key found for kid={kid} in JWKS

Error message

No matching key found for kid={kid} in JWKS

What it means

The JWKS document has keys, but none carries a `kid` matching the `kid` header of the incoming client assertion, and there is more than one key so the library refuses to guess. It is raised from `_extract_public_key_from_jwks` during assertion validation.

Source

Thrown at fastmcp_slim/fastmcp/server/auth/cimd.py:694

        keys = jwks.get("keys", [])
        if not keys:
            raise ValueError("JWKS document contains no keys")

        matching_key = None
        for key in keys:
            if kid and key.get("kid") == kid:
                matching_key = key
                break

        if not matching_key:
            # If no kid match, try first key as fallback
            if len(keys) == 1:
                matching_key = keys[0]
                self.logger.warning(
                    "No matching kid in JWKS, using single available key"
                )
            else:
                raise ValueError(f"No matching key found for kid={kid} in JWKS")

        # Convert JWK to PEM
        try:
            return _jwk_to_pem(matching_key)
        except (JoseError, TypeError, ValueError) as e:
            raise ValueError(f"Failed to convert JWK to PEM: {e}") from e


class CIMDClientManager:
    """Manages all CIMD client operations for OAuth proxy.

    This class encapsulates:
    - CIMD client detection
    - Document fetching and validation
    - Synthetic OAuth client creation
    - Private key JWT assertion validation

    This allows the OAuth proxy to delegate all CIMD-specific logic to a

View on GitHub (pinned to 1f02114297)

Solutions

  1. Make the client sign assertions with the key whose kid matches an entry in the JWKS
  2. Ensure the JWKS publishes all currently-valid signing keys with correct `kid` values
  3. If the client omits `kid`, either add the header or reduce the JWKS to a single key so the fallback applies
  4. Verify the kid strings match exactly (no casing/whitespace differences) between token and JWKS

Example fix

// before (token kid not in JWKS)
{"keys": [{"kid": "key-1", ...}, {"kid": "key-2", ...}]}
// token header: {"kid": "key-old", "alg": "RS256"}
// after — publish/rotate so kid matches
{"keys": [{"kid": "key-old", ...}, {"kid": "key-1", ...}]}
Defensive patterns

Strategy: validation

Validate before calling

import jwt
header = jwt.get_unverified_header(assertion)
kids = {k.get("kid") for k in jwks["keys"]}
if header.get("kid") not in kids and len(jwks["keys"]) > 1:
    raise ValueError(f"kid {header.get('kid')!r} not in JWKS and JWKS is multi-key")

Type guard

def kid_in_jwks(token_kid: str | None, jwks: dict) -> bool:
    if token_kid is None:
        return len(jwks.get("keys", [])) == 1  # only single-key fallback works
    return any(k.get("kid") == token_kid for k in jwks.get("keys", []))

Try / catch

try:
    key = extract_public_key(jwks, kid)
except ValueError as e:
    if str(e).startswith("No matching key found for kid="):
        logger.warning("Key rotation mismatch: token kid=%s not published", kid)
    raise

Prevention

When it happens

Trigger: Token's JWT header `kid` does not equal any `kid` in the JWKS while the JWKS contains 2+ keys. With exactly one key the library falls back to using it (with a warning) instead of raising.

Common situations: Issuer rotated signing keys but kept the old key in the JWKS; client signed with a key whose kid isn't published yet; kid string mismatch (case, whitespace) between token header and JWKS entry; multiple keys published and the client library omits kid in the token header.

Related errors


AI-assisted analysis of PrefectHQ/fastmcp@1f02114297 (2026-08-29). Data as JSON: /api/errors/45220b682b4699f0. Report an issue: GitHub.