PrefectHQ/fastmcp · error · ValueError

Failed to extract key ID from token: {e}

Error message

Failed to extract key ID from token: {e}

What it means

Raised by _extract_public_key_from_jwks when the token's JOSE header cannot be decoded to obtain the 'kid' (key ID). The method base64-decodes and JSON-parses the first dot-separated segment; malformed segments, non-JSON payloads, or structural problems raise this wrapped ValueError.

Source

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

        Args:
            token: JWT token to extract kid from
            jwks: JWKS document containing keys

        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"

View on GitHub (pinned to 1f02114297)

Solutions

  1. Verify the token has three dot-separated base64url segments and a JSON header containing 'kid'
  2. Confirm you are passing the signed client assertion JWT, not an opaque access token
  3. Log the first segment (header) and decode it manually to spot corruption/encoding issues

Example fix

// before
key = validator._extract_public_key_from_jwks(access_token, jwks)  # opaque token
// after
key = validator._extract_public_key_from_jwks(client_assertion_jwt, jwks)  # real JWT with kid
Defensive patterns

Strategy: try-catch

Validate before calling

import base64, json
def token_has_decodable_kid(token: str) -> bool:
    try:
        h = token.split(".")[0]
        h += "=" * (4 - len(h) % 4)
        return "kid" in json.loads(base64.urlsafe_b64decode(h))
    except Exception:
        return False

Type guard

def is_well_formed_jwt(token: str) -> bool:
    parts = token.split(".")
    if len(parts) != 3:
        return False
    try:
        h = parts[0] + "=" * (4 - len(parts[0]) % 4)
        json.loads(base64.urlsafe_b64decode(h))
        return True
    except Exception:
        return False

Try / catch

try:
    key = extract_public_key_from_jwks(token, jwks)
except ValueError as e:
    if "Failed to extract key ID" in str(e):
        raise AssertionError(f"token is not a decodable JWT: {token[:20]}...") from e
    raise

Prevention

When it happens

Trigger: Passing a token that is not a well-formed three-part JWT (missing dots, garbage prefix); a JWT header that is not valid base64url/JSON; passing a JWE (encrypted token) whose header is not a plain JOSE header; truncation of the token in transit.

Common situations: Debugging with placeholder/opaque tokens instead of real JWTs; clients accidentally sending access tokens (opaque) where a client assertion is required; newline/whitespace corruption of tokens copied from logs or config files.

Related errors


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