PrefectHQ/fastmcp · error · ValueError

Multiple keys in JWKS but no key ID (kid) in token

Error message

Multiple keys in JWKS but no key ID (kid) in token

What it means

The token has no kid header, so the library tries to select a key from the JWKS automatically. It only permits this when the JWKS contains exactly one key; with multiple keys it cannot know which one signed the token and raises this error instead of guessing. The fix is on the token issuer side: sign tokens with a kid header.

Source

Thrown at fastmcp_slim/fastmcp/server/auth/providers/jwt.py:428

                            "JWKS key lookup failed: key ID '%s' is present "
                            "but its key type is unsupported",
                            kid,
                        )
                        raise ValueError(
                            f"Key ID '{kid}' found in JWKS but its key type "
                            "is unsupported"
                        )
                    self.logger.debug(
                        "JWKS key lookup failed: key ID '%s' not found", kid
                    )
                    raise ValueError(f"Key ID '{kid}' not found in JWKS")
                return self._jwks_cache[kid]
            else:
                # No kid in token - only allow if there's exactly one key
                if len(self._jwks_cache) == 1:
                    return next(iter(self._jwks_cache.values()))
                elif len(self._jwks_cache) > 1:
                    raise ValueError(
                        "Multiple keys in JWKS but no key ID (kid) in token"
                    )
                else:
                    raise ValueError("No keys found in JWKS")

        except (SSRFError, SSRFFetchError) as e:
            self.logger.debug("JWKS fetch blocked by SSRF protection: %s", e)
            raise ValueError(f"Failed to fetch JWKS: {e}") from e
        except httpx2.HTTPError as e:
            raise ValueError(f"Failed to fetch JWKS: {e}") from e
        except json.JSONDecodeError as e:
            raise ValueError(f"Invalid JWKS JSON: {e}") from e
        except (JoseError, TypeError, KeyError, ValueError) as e:
            self.logger.debug("JWKS key processing failed: %s", e)
            raise ValueError(f"Failed to process JWKS: {e}") from e

    async def _fetch_jwks(self) -> dict[str, Any]:
        """Fetch JWKS data, using SSRF-safe or standard fetch based on config."""

View on GitHub (pinned to 1f02114297)

Solutions

  1. Configure the token issuer to include the kid header in signed JWTs (most IdPs do this by default once multiple keys exist)
  2. Ensure only one signing key is active/published at the JWKS if you must support kid-less tokens
  3. If you control signing manually, pass the key id when signing (e.g. jwt.encode(..., headers={"kid": key_id}) with PyJWT)
  4. Upgrade the issuing SDK so it sets kid automatically

Example fix

// before
token = jwt.encode(claims, private_key, algorithm="RS256")
// after
token = jwt.encode(claims, private_key, algorithm="RS256", headers={"kid": "my-key-id"})
Defensive patterns

Strategy: validation

Validate before calling

import jwt
header = jwt.get_unverified_header(token)
if not header.get("kid"):
    raise RuntimeError("Token missing kid header; issuer must include kid when JWKS has multiple keys")

Type guard

def token_has_kid(token: str) -> bool:
    return bool(jwt.get_unverified_header(token).get("kid"))

Try / catch

try:
    claims = await verifier.verify_token(token)
except ValueError as e:
    if "no key ID (kid) in token" in str(e):
        raise RuntimeError("Issuing SDK must sign tokens with a kid header") from e
    raise

Prevention

When it happens

Trigger: Calling verify_token on a JWT whose header lacks kid while the configured jwks_uri resolves to a key set with two or more keys.

Common situations: IdP rotated to multiple signing keys (rotation implies >1 published key) but a client/token-issuing component still omits kid; hand-rolled token signing without kid; older SDK that didn't set kid, run against a multi-key IdP.

Related errors


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