{"record":{"id":"db161bdd18f374c4","repo":"PrefectHQ/fastmcp","slug":"key-id-kid-not-found-in-jwks","errorCode":null,"errorMessage":"Key ID '{kid}' not found in JWKS","messagePattern":"Key ID '(.+?)' not found in JWKS","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"fastmcp_slim/fastmcp/server/auth/providers/jwt.py","lineNumber":421,"sourceCode":"            self._jwks_cache_time = current_time\n\n            # Select the appropriate key\n            if kid:\n                if kid not in self._jwks_cache:\n                    if kid in skipped_kids:\n                        self.logger.debug(\n                            \"JWKS key lookup failed: key ID '%s' is present \"\n                            \"but its key type is unsupported\",\n                            kid,\n                        )\n                        raise ValueError(\n                            f\"Key ID '{kid}' found in JWKS but its key type \"\n                            \"is unsupported\"\n                        )\n                    self.logger.debug(\n                        \"JWKS key lookup failed: key ID '%s' not found\", kid\n                    )\n                    raise ValueError(f\"Key ID '{kid}' not found in JWKS\")\n                return self._jwks_cache[kid]\n            else:\n                # No kid in token - only allow if there's exactly one key\n                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:","sourceCodeStart":403,"sourceCodeEnd":439,"githubUrl":"https://github.com/PrefectHQ/fastmcp/blob/1f021142978e0861cd910c8df4e8074bc7cf3978/fastmcp_slim/fastmcp/server/auth/providers/jwt.py#L403-L439","documentation":"The token carries a kid whose corresponding key could not be found in the fetched JWKS — either the kid matched no entry, or the only matching entry was skipped. The library raises this so signature verification fails fast instead of attempting verification with the wrong key. It almost always means the token was signed by a key the identity provider no longer publishes (or by a different provider entirely).","triggerScenarios":"Calling verify_token on a JWT with a kid that is absent from the JWKS at the configured jwks_uri, including during key rotation before the local JWKS cache (cache TTL) refreshes.","commonSituations":"IdP rotated signing keys and the verifier's cached JWKS is stale until the TTL expires; token issued by a different environment/tenant (staging token verified against prod IdP); typo in jwks_uri pointing at the wrong realm; tokens minted by a deprecated IdP.","solutions":["Wait for/reduce the JWKS cache TTL (or restart the process) so the verifier re-fetches the current JWKS and picks up the rotated key","Compare the token's kid (decode the JWT header) against the kids currently published at your jwks_uri to confirm the mismatch","Confirm jwks_uri points to the correct issuer/realm that actually issued the token","Re-issue tokens with the current signing key; discard tokens signed by retired keys"],"exampleFix":"// before: stale cache after key rotation\nverifier = JWTVerifier(jwks_uri=..., cache_ttl=3600)\n// after: shorten TTL so rotated keys are picked up quickly\nverifier = JWTVerifier(jwks_uri=..., cache_ttl=300)\n","handlingStrategy":"retry","validationCode":"import jwt, httpx\nkid = jwt.get_unverified_header(token).get(\"kid\")\npublished = {k.get(\"kid\") for k in httpx.get(jwks_uri).json()[\"keys\"]}\nif kid and kid not in published:\n    # token signed by an unpublished/retired key\n    raise RuntimeError(f\"kid {kid!r} not in current JWKS\")","typeGuard":"def kid_in_jwks(token: str, jwks_keys: list[dict]) -> bool:\n    kid = jwt.get_unverified_header(token).get(\"kid\")\n    return kid is not None and any(k.get(\"kid\") == kid for k in jwks_keys)","tryCatchPattern":"try:\n    claims = await verifier.verify_token(token)\nexcept ValueError as e:\n    if \"not found in JWKS\" in str(e):\n        # possible key rotation: allow a short retry after cache refresh\n        await asyncio.sleep(0.5)\n        claims = await verifier.verify_token(token)\n    else:\n        raise","preventionTips":["Keep the JWKS cache TTL short relative to your IdP's key-rotation cadence","Monitor IdP key-rotation events and proactively flush the verifier cache","Verify tokens against the same IdP environment that issued them (avoid staging/prod mixups)","Double-check jwks_uri points to the issuer's realm"],"tags":["auth","jwt","jwks","key-rotation"],"backgroundTag":"kid-not-found-in-jwks","analyzedSha":"1f021142978e0861cd910c8df4e8074bc7cf3978","analyzedAt":"2026-08-29T14:31:16.082Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}