PrefectHQ/fastmcp · error · ValueError

Failed to process JWKS: {e}

Error message

Failed to process JWKS: {e}

What it means

This ValueError from JWTVerifier._get_jwks_key means the JWKS JSON was fetched and parsed but key processing failed — the fetched data isn't shaped like a JWKS (no 'keys' member, wrong types), or an inner ValueError (e.g. 'No keys found in JWKS', kid not found, multiple keys without kid) propagated from the try block. The library wraps JoseError/TypeError/KeyError/ValueError into this single message; enable debug logging to see the underlying cause.

Source

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

                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."""
        if not self.jwks_uri:
            raise ValueError("JWKS URI not configured")

        if self.ssrf_safe:
            content = await ssrf_safe_fetch(
                self.jwks_uri,
                max_size=65536,
                timeout=10.0,
                overall_timeout=30.0,
            )
            return json.loads(content)
        else:
            async with (
                contextlib.nullcontext(self._http_client)
                if self._http_client is not None

View on GitHub (pinned to 1f02114297)

Solutions

  1. Check debug logs ('JWKS key processing failed') for the wrapped underlying message — it names the exact cause (missing keys, kid mismatch, etc.).
  2. Align the verifier's issuer and jwks_uri with the IdP that actually signed the tokens (same realm/tenant).
  3. Set the algorithm to one matching the JWKS key types (RS256 for RSA keys, ES256 for EC P-256, etc.).
  4. If the IdP rotated keys, wait for cache expiry or restart the verifier so the fresh key set is loaded; ensure tokens carry a kid header when multiple keys are published.

Example fix

// before
verifier = JWTVerifier(issuer="https://auth.a.com", jwks_uri="https://auth.b.com/.well-known/jwks.json")  # kid never matches
// after
verifier = JWTVerifier(issuer="https://auth.a.com", jwks_uri="https://auth.a.com/.well-known/jwks.json", algorithm="RS256")
Defensive patterns

Strategy: validation

Validate before calling

import httpx, json, base64

async def jwks_has_compatible_key(uri: str, kty: str = "RSA") -> bool:
    async with httpx.AsyncClient(timeout=10.0) as client:
        keys = (await client.get(uri)).json().get("keys", [])
    return any(isinstance(k, dict) and k.get("kty") == kty for k in keys)

# also confirm your tokens carry a kid header matching one of the JWKS kids

Type guard

def jwks_key_for_kid(jwks: dict, kid: str) -> dict | None:
    keys = jwks.get("keys", []) if isinstance(jwks, dict) else []
    return next((k for k in keys if isinstance(k, dict) and k.get("kid") == kid), None)

Try / catch

try:
    claims = await verifier.verify_token(token)
except ValueError as e:
    if "Failed to process JWKS" in str(e):
        logger.error("JWKS content incompatible with verifier config: %s", e)
        return None  # inspect debug logs for the wrapped cause

Prevention

When it happens

Trigger: verify_token -> _get_jwks_key when: the JSON is valid but not a JWKS object (missing 'keys'); the key set is empty ('No keys found in JWKS'); the token's kid is absent from the set ('Key ID ... not found in JWKS'); the token has no kid while the set holds multiple keys; or the key's kty is incompatible with the configured algorithm.

Common situations: IdP rotated keys so an old cached kid is gone; token issued by a different realm/issuer than the JWKS being consulted; algorithm configured as RS256 while the JWKS publishes only EC (or vice versa); pointing two different IdPs' issuer and jwks_uri at each other.

Related errors


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