{"record":{"id":"f2b1653a5be8f286","repo":"PrefectHQ/fastmcp","slug":"cimd-document-must-have-jwks-uri-or-jwks-for-priva","errorCode":null,"errorMessage":"CIMD document must have jwks_uri or jwks for private_key_jwt","messagePattern":"CIMD document must have jwks_uri or jwks for private_key_jwt","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"fastmcp_slim/fastmcp/server/auth/cimd.py","lineNumber":578,"sourceCode":"                    jwks_uri=jwks_uri_str,\n                    issuer=client_id,\n                    audience=token_endpoint,\n                    ssrf_safe=True,\n                )\n                if len(self._verifier_cache) >= self._verifier_cache_max_size:\n                    oldest_key = next(iter(self._verifier_cache))\n                    del self._verifier_cache[oldest_key]\n                self._verifier_cache[cache_key] = verifier\n        elif cimd_doc.jwks:\n            # Inline JWKS — no caching since the key is embedded\n            public_key = self._extract_public_key_from_jwks(assertion, cimd_doc.jwks)\n            verifier = _JWTVerifier(\n                public_key=public_key,\n                issuer=client_id,\n                audience=token_endpoint,\n            )\n        else:\n            raise ValueError(\n                \"CIMD document must have jwks_uri or jwks for private_key_jwt\"\n            )\n\n        # 2. Verify JWT using JWTVerifier (handles signature, exp, iss, aud)\n        access_token = await verifier.load_access_token(assertion)\n        if not access_token:\n            raise ValueError(\"Invalid JWT assertion\")\n\n        claims = access_token.claims\n\n        # 3. Validate assertion lifetime (exp and iat)\n        now = time.time()\n        exp = claims.get(\"exp\")\n        iat = claims.get(\"iat\")\n\n        if not exp:\n            raise ValueError(\"Assertion must include exp claim\")\n","sourceCodeStart":560,"sourceCodeEnd":596,"githubUrl":"https://github.com/PrefectHQ/fastmcp/blob/1f021142978e0861cd910c8df4e8074bc7cf3978/fastmcp_slim/fastmcp/server/auth/cimd.py#L560-L596","documentation":"validate_assertion verifies private_key_jwt client assertions against the client's keys. The CIMD document must supply those keys either as jwks_uri or inline jwks; a document with neither (and token_endpoint_auth_method implying key-based auth) makes signature verification impossible, so a ValueError is raised.","triggerScenarios":"validate_private_key_jwt / validate_assertion invoked with a CIMDDocument whose jwks_uri and jwks are both None — typically a document with token_endpoint_auth_method 'none' being used for private_key_jwt, or a malformed document.","commonSituations":"Client documents declaring token_endpoint_auth_method='none' while the client actually sends private_key_jwt assertions; metadata documents missing key material fields entirely; key rotation scripts that removed jwks fields.","solutions":["Update the CIMD document to include either jwks_uri (public HTTPS JWKS URL) or inline jwks","Ensure token_endpoint_auth_method in the document is 'private_key_jwt' if the client signs assertions","If the client truly uses no auth ('none'), stop sending private_key_jwt assertions from the client","Re-fetch the document — the server may have a cached older version without the keys"],"exampleFix":"// before\n{\"client_id\": \"https://app.example.com/client.json\",\n \"token_endpoint_auth_method\": \"private_key_jwt\"}\n// after\n{\"client_id\": \"https://app.example.com/client.json\",\n \"token_endpoint_auth_method\": \"private_key_jwt\",\n \"jwks_uri\": \"https://keys.app.example.com/jwks.json\"}","handlingStrategy":"validation","validationCode":"def can_do_private_key_jwt(doc: dict) -> bool:\n    if doc.get(\"token_endpoint_auth_method\") != \"private_key_jwt\":\n        return True\n    return bool(doc.get(\"jwks_uri\") or doc.get(\"jwks\"))","typeGuard":"def has_key_material(doc: object) -> bool:\n    return isinstance(doc, dict) and bool(doc.get(\"jwks_uri\") or doc.get(\"jwks\"))","tryCatchPattern":"try:\n    await manager.validate_private_key_jwt(doc, assertion, token_endpoint)\nexcept ValueError as e:\n    if \"jwks_uri or jwks\" in str(e):\n        raise InvalidClientError(\"client document lacks key material\") from e\n    raise","preventionTips":["Always publish jwks_uri or inline jwks when using private_key_jwt","Keep token_endpoint_auth_method consistent with how the client actually authenticates","Re-fetch documents after the client updates key material"],"tags":["oauth","cimd","jwt","private-key-jwt","validation"],"backgroundTag":"missing-jwks-configuration","analyzedSha":"1f021142978e0861cd910c8df4e8074bc7cf3978","analyzedAt":"2026-08-29T14:31:16.082Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}