PrefectHQ/fastmcp · error · ValueError
JWKS document contains no keys
Error message
JWKS document contains no keys
What it means
This error means the JWKS document fetched (or supplied) for a CIMD client contains an empty `keys` array, so no public key can be extracted to verify a client assertion (private_key_jwt). The library raises it early rather than failing later with a confusing 'no matching key' error. It is a ValueError wrapping the key-selection step of assertion validation.
Source
Thrown at fastmcp_slim/fastmcp/server/auth/cimd.py:678
Returns:
PEM-encoded public key
Raises:
ValueError: If key cannot be found or extracted
"""
# Extract kid from token header
try:
header_b64 = token.split(".")[0]
header_b64 += "=" * (4 - len(header_b64) % 4) # Add padding
header = json.loads(base64.urlsafe_b64decode(header_b64))
kid = header.get("kid")
except (IndexError, ValueError, json.JSONDecodeError) as e:
raise ValueError(f"Failed to extract key ID from token: {e}") from e
# Find matching key in JWKS
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 PEMView on GitHub (pinned to 1f02114297)
Solutions
- Inspect the JWKS document at the CIMD client's jwks_uri and confirm it contains a non-empty `keys` array with the signing key
- Fix the issuer's JWKS publishing endpoint so it serves the public keys (e.g. re-export the key set)
- If the JWKS is embedded in the CIMD document, update the metadata to include the `keys` array
- Re-fetch after the issuer rotates keys — the empty set may be transient during rotation
Example fix
// before (bad JWKS served by issuer)
{"keys": []}
// after
{"keys": [{"kty": "RSA", "kid": "key-1", "n": "...", "e": "AQAB"}]} Defensive patterns
Strategy: validation
Validate before calling
import json, urllib.request
jwks = json.load(urllib.request.urlopen(client_meta["jwks_uri"]))
assert isinstance(jwks.get("keys"), list) and len(jwks["keys"]) > 0, "JWKS has no keys" Type guard
def has_keys(jwks: dict) -> bool:
keys = jwks.get("keys")
return isinstance(keys, list) and len(keys) > 0 Try / catch
try:
key = extract_public_key(jwks, kid)
except ValueError as e:
if "contains no keys" in str(e):
logger.error("Issuer JWKS is empty; check jwks_uri %s", jwks_uri)
raise Prevention
- Verify the issuer's jwks_uri serves a valid non-empty JWKS before registering the CIMD client
- Monitor the JWKS endpoint for availability and content changes
- Refresh/re-fetch the JWKS after issuer key rotation
- Validate the JWKS document with a schema check during onboarding of new issuers
When it happens
Trigger: `_extract_public_key_from_jwks` is called via `validate_assertion` with a JWKS document (from the client's CIMD metadata, e.g. `jwks_uri` or embedded `jwks`) whose parsed JSON has no `keys` member or `keys: []`.
Common situations: Authorization server publishes a malformed/placeholder JWKS at the jwks_uri in the CIMD document; a static/mocked JWKS file used in testing is empty; the JWKS fetch succeeded but the issuer rotated keys and returned an empty set; misconfigured jwks_uri pointing to a non-JWKS JSON doc that happens to parse.
Related errors
- CIMD jwks_uri failed SSRF validation: {e}
- CIMD document must have jwks_uri or jwks for private_key_jwt
- Invalid JWT assertion
- No matching key found for kid={kid} in JWKS
- Client must have CIMD document for private_key_jwt
AI-assisted analysis of PrefectHQ/fastmcp@1f02114297 (2026-08-29).
Data as JSON: /api/errors/d7e2952a33084bf4.
Report an issue: GitHub.