{"record":{"id":"da726ea18b53bace","repo":"PrefectHQ/fastmcp","slug":"failed-to-process-jwks-e","errorCode":null,"errorMessage":"Failed to process JWKS: {e}","messagePattern":"Failed to process JWKS: (.+?)","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"fastmcp_slim/fastmcp/server/auth/providers/jwt.py","lineNumber":443,"sourceCode":"                if len(self._jwks_cache) == 1:\n                    return next(iter(self._jwks_cache.values()))\n                elif len(self._jwks_cache) > 1:\n                    raise ValueError(\n                        \"Multiple keys in JWKS but no key ID (kid) in token\"\n                    )\n                else:\n                    raise ValueError(\"No keys found in JWKS\")\n\n        except (SSRFError, SSRFFetchError) as e:\n            self.logger.debug(\"JWKS fetch blocked by SSRF protection: %s\", e)\n            raise ValueError(f\"Failed to fetch JWKS: {e}\") from e\n        except httpx2.HTTPError as e:\n            raise ValueError(f\"Failed to fetch JWKS: {e}\") from e\n        except json.JSONDecodeError as e:\n            raise ValueError(f\"Invalid JWKS JSON: {e}\") from e\n        except (JoseError, TypeError, KeyError, ValueError) as e:\n            self.logger.debug(\"JWKS key processing failed: %s\", e)\n            raise ValueError(f\"Failed to process JWKS: {e}\") from e\n\n    async def _fetch_jwks(self) -> dict[str, Any]:\n        \"\"\"Fetch JWKS data, using SSRF-safe or standard fetch based on config.\"\"\"\n        if not self.jwks_uri:\n            raise ValueError(\"JWKS URI not configured\")\n\n        if self.ssrf_safe:\n            content = await ssrf_safe_fetch(\n                self.jwks_uri,\n                max_size=65536,\n                timeout=10.0,\n                overall_timeout=30.0,\n            )\n            return json.loads(content)\n        else:\n            async with (\n                contextlib.nullcontext(self._http_client)\n                if self._http_client is not None","sourceCodeStart":425,"sourceCodeEnd":461,"githubUrl":"https://github.com/PrefectHQ/fastmcp/blob/1f021142978e0861cd910c8df4e8074bc7cf3978/fastmcp_slim/fastmcp/server/auth/providers/jwt.py#L425-L461","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Check debug logs ('JWKS key processing failed') for the wrapped underlying message — it names the exact cause (missing keys, kid mismatch, etc.).","Align the verifier's issuer and jwks_uri with the IdP that actually signed the tokens (same realm/tenant).","Set the algorithm to one matching the JWKS key types (RS256 for RSA keys, ES256 for EC P-256, etc.).","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."],"exampleFix":"// before\nverifier = JWTVerifier(issuer=\"https://auth.a.com\", jwks_uri=\"https://auth.b.com/.well-known/jwks.json\")  # kid never matches\n// after\nverifier = JWTVerifier(issuer=\"https://auth.a.com\", jwks_uri=\"https://auth.a.com/.well-known/jwks.json\", algorithm=\"RS256\")","handlingStrategy":"validation","validationCode":"import httpx, json, base64\n\nasync def jwks_has_compatible_key(uri: str, kty: str = \"RSA\") -> bool:\n    async with httpx.AsyncClient(timeout=10.0) as client:\n        keys = (await client.get(uri)).json().get(\"keys\", [])\n    return any(isinstance(k, dict) and k.get(\"kty\") == kty for k in keys)\n\n# also confirm your tokens carry a kid header matching one of the JWKS kids","typeGuard":"def jwks_key_for_kid(jwks: dict, kid: str) -> dict | None:\n    keys = jwks.get(\"keys\", []) if isinstance(jwks, dict) else []\n    return next((k for k in keys if isinstance(k, dict) and k.get(\"kid\") == kid), None)","tryCatchPattern":"try:\n    claims = await verifier.verify_token(token)\nexcept ValueError as e:\n    if \"Failed to process JWKS\" in str(e):\n        logger.error(\"JWKS content incompatible with verifier config: %s\", e)\n        return None  # inspect debug logs for the wrapped cause","preventionTips":["Keep issuer and jwks_uri pointed at the same realm/tenant that signs your tokens.","Match the configured algorithm to the JWKS key types (RSA->RS256, EC->ES256).","Require kid in token headers when the IdP publishes multiple keys.","Handle IdP key rotation: allow re-fetch after cache expiry and monitor kid-mismatch logs."],"tags":["jwt","jwks","configuration"],"backgroundTag":"jwks-key-not-found","analyzedSha":"1f021142978e0861cd910c8df4e8074bc7cf3978","analyzedAt":"2026-08-29T14:31:16.082Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}