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 aView on GitHub (pinned to 1f02114297)
Solutions
- Make the client sign assertions with the key whose kid matches an entry in the JWKS
- Ensure the JWKS publishes all currently-valid signing keys with correct `kid` values
- If the client omits `kid`, either add the header or reduce the JWKS to a single key so the fallback applies
- 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
- Keep the token-signing key's kid present in the published JWKS at all times (publish new key before signing with it)
- Ensure the client library always sets the kid header
- Use exact string equality for kid values (watch casing/whitespace)
- During rotation, keep both old and new keys in the JWKS until all tokens signed with the old key expire
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
- JWKS document contains no keys
- Invalid client_assertion_type: expected {JWT_BEARER_ASSERTIO
- CIMD jwks_uri failed SSRF validation: {e}
- CIMD document must have jwks_uri or jwks for private_key_jwt
- Invalid JWT assertion
AI-assisted analysis of PrefectHQ/fastmcp@1f02114297 (2026-08-29).
Data as JSON: /api/errors/45220b682b4699f0.
Report an issue: GitHub.