{"record":{"id":"d68ed6fac3a03bb6","repo":"BerriAI/litellm","slug":"jwt-auth-oidc-discovery-document-at-url-does-no","errorCode":null,"errorMessage":"JWT Auth: OIDC discovery document at {url} does not contain a 'jwks_uri' field.","messagePattern":"JWT Auth: OIDC discovery document at (.+?) does not contain a 'jwks_uri' field\\.","errorType":"exception","errorClass":"Exception","httpStatus":null,"severity":"error","filePath":"litellm/proxy/auth/handle_jwt.py","lineNumber":632,"sourceCode":"        cache_key: Final = f\"litellm_oidc_discovery_{url}\"\n        cached_jwks_uri: Final = await self.user_api_key_cache.async_get_cache(cache_key)\n        if cached_jwks_uri is not None:\n            return cached_jwks_uri\n\n        verbose_proxy_logger.debug(\"JWT Auth: Fetching OIDC discovery document from %s\", url)\n        response: Final = await self.http_handler.get(url)\n        if response.status_code != 200:\n            raise Exception(\n                f\"JWT Auth: OIDC discovery endpoint {url} returned status {response.status_code}: {response.text}\"\n            )\n        try:\n            discovery: Final = response.json()\n        except Exception as e:\n            raise Exception(f\"JWT Auth: Failed to parse OIDC discovery document at {url}: {e}\")\n\n        jwks_uri: Final = discovery.get(\"jwks_uri\")\n        if not jwks_uri:\n            raise Exception(f\"JWT Auth: OIDC discovery document at {url} does not contain a 'jwks_uri' field.\")\n\n        verbose_proxy_logger.debug(\"JWT Auth: Resolved OIDC discovery %s -> jwks_uri=%s\", url, jwks_uri)\n        await self.user_api_key_cache.async_set_cache(\n            key=cache_key,\n            value=jwks_uri,\n            ttl=self._get_public_key_cache_ttl(),\n        )\n        return jwks_uri\n\n    def _get_public_key_cache_ttl(self) -> float:\n        litellm_jwtauth: Final = getattr(self, \"litellm_jwtauth\", None)\n        if litellm_jwtauth is None:\n            return 600\n        return litellm_jwtauth.public_key_ttl\n\n    async def _get_public_key_from_jwks_url(self, jwks_url: str, kid: str | None) -> dict:\n        resolved_jwks_url: Final = await self._resolve_jwks_url(jwks_url)\n        cache_key: Final = f\"litellm_jwt_auth_keys_{resolved_jwks_url}\"","sourceCodeStart":614,"sourceCodeEnd":650,"githubUrl":"https://github.com/BerriAI/litellm/blob/77b7c6c40c0c5aa5fbcb1d6a1825ac39ca8829b8/litellm/proxy/auth/handle_jwt.py#L614-L650","documentation":"Raised in _resolve_jwks_url when the OIDC discovery document was fetched and parsed as JSON successfully, but the parsed object has no jwks_uri field. The proxy needs jwks_uri to locate the signing keys, so key resolution aborts.","triggerScenarios":"JWT_PUBLIC_KEY_URL contains .well-known/openid-configuration but the endpoint returns some other valid JSON - e.g. a JWKS document itself ({\"keys\": [...]}) served under a matching path, or an unrelated API payload - which lacks jwks_uri.","commonSituations":"Pointing JWT_PUBLIC_KEY_URL at the JWKS endpoint while the URL string still contains .well-known/openid-configuration (forcing the discovery code path); a gateway that rewrites the discovery path to another JSON API; custom servers that reuse the well-known path for different payloads.","solutions":["If you meant to use the JWKS directly, make sure JWT_PUBLIC_KEY_URL is the keys endpoint URL that does NOT contain .well-known/openid-configuration - discovery is only triggered by that substring","If you want discovery, curl the URL and confirm the JSON top level includes jwks_uri","Fix the IdP/gateway routing so the well-known path serves the genuine discovery document"],"exampleFix":"# before: JWKS URL mislabeled with the discovery substring, triggering discovery parsing\nJWT_PUBLIC_KEY_URL=https://idp.example.com/jwks/.well-known/openid-configuration-certs\n\n# after: plain JWKS URL - discovery code path is skipped entirely\nJWT_PUBLIC_KEY_URL=https://idp.example.com/oidc/jwks","handlingStrategy":"validation","validationCode":"import httpx\n\nasync def discovery_doc_is_valid(url: str) -> bool:\n    async with httpx.AsyncClient() as c:\n        doc = (await c.get(url)).json()\n    return isinstance(doc, dict) and bool(doc.get(\"jwks_uri\"))","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Remember discovery only triggers when the URL contains '.well-known/openid-configuration' - keep raw JWKS URLs free of that substring","Verify a freshly copied discovery URL returns a document containing jwks_uri before deploying"],"tags":["jwt","oidc","discovery","jwks","authentication"],"backgroundTag":"oidc-discovery-endpoint-failed","analyzedSha":"77b7c6c40c0c5aa5fbcb1d6a1825ac39ca8829b8","analyzedAt":"2026-08-18T11:44:31.656Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}