{"record":{"id":"1deac8f9b272f11b","repo":"BerriAI/litellm","slug":"jwt-auth-oidc-discovery-endpoint-url-returned-s","errorCode":null,"errorMessage":"JWT Auth: OIDC discovery endpoint {url} returned status {response.status_code}: {response.text}","messagePattern":"JWT Auth: OIDC discovery endpoint (.+?) returned status (.+?): (.+?)","errorType":"exception","errorClass":"Exception","httpStatus":null,"severity":"error","filePath":"litellm/proxy/auth/handle_jwt.py","lineNumber":622,"sourceCode":"    async def _resolve_jwks_url(self, url: str) -> str:\n        \"\"\"\n        If url points to an OIDC discovery document (*.well-known/openid-configuration),\n        fetch it and return the jwks_uri contained within.  Otherwise return url unchanged.\n        This lets JWT_PUBLIC_KEY_URL be set to a well-known discovery endpoint instead of\n        requiring operators to manually find the JWKS URL.\n        \"\"\"\n        if \".well-known/openid-configuration\" not in url:\n            return url\n\n        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","sourceCodeStart":604,"sourceCodeEnd":640,"githubUrl":"https://github.com/BerriAI/litellm/blob/77b7c6c40c0c5aa5fbcb1d6a1825ac39ca8829b8/litellm/proxy/auth/handle_jwt.py#L604-L640","documentation":"Raised in JWTAuthBase._resolve_jwks_url (litellm/proxy/auth/handle_jwt.py). When JWT_PUBLIC_KEY_URL points at an OIDC discovery document (URL containing .well-known/openid-configuration), the proxy fetches it to find jwks_uri; if the endpoint answers with any non-200 status, this exception aborts JWT auth. The resolved jwks_uri is cached, so this fires on the first uncached lookup.","triggerScenarios":"JWT_PUBLIC_KEY_URL=https://idp.example.com/.well-known/openid-configuration and the IdP returns 404 (wrong path/tenant), 401/403 (discovery behind auth), or 5xx (IdP error) when the proxy fetches it during key resolution.","commonSituations":"Wrong tenant/region segment in the discovery URL (common with Keycloak realms and Auth0 domains); transient IdP outage at startup or during cache expiry; the discovery endpoint requires credentials; a corporate proxy returning 407/502.","solutions":["curl the exact discovery URL from the proxy host and confirm it returns 200 with JSON containing jwks_uri","Fix the tenant/realm path (e.g. Keycloak: /realms/<realm>/.well-known/openid-configuration; Auth0: correct domain)","Bypass discovery entirely: set JWT_PUBLIC_KEY_URL to the JWKS URL itself (the literal keys endpoint), which skips this fetch","If the failure was a transient 5xx, it will be retried on the next auth attempt once the IdP recovers - check IdP status"],"exampleFix":"# before: discovery URL that 404s (wrong realm)\nJWT_PUBLIC_KEY_URL=https://keycloak.example.com/realms/wrong-realm/.well-known/openid-configuration\n\n# after: point directly at the JWKS endpoint (no discovery step)\nJWT_PUBLIC_KEY_URL=https://keycloak.example.com/realms/my-realm/protocol/openid-connect/certs","handlingStrategy":"validation","validationCode":"import httpx, os\n\nasync def assert_discovery_reachable() -> None:\n    url = os.environ[\"JWT_PUBLIC_KEY_URL\"]\n    if \".well-known/openid-configuration\" not in url:\n        return  # plain JWKS URL, discovery step skipped\n    async with httpx.AsyncClient() as c:\n        r = await c.get(url)\n        r.raise_for_status()\n        assert \"jwks_uri\" in r.json(), \"discovery document missing jwks_uri\"","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Add a startup/canary check that curls the discovery URL from the proxy host","Prefer pointing JWT_PUBLIC_KEY_URL directly at the JWKS endpoint to remove the discovery dependency","Double-check realm/tenant path segments (Keycloak realms, Auth0 domains) when copying IdP URLs"],"tags":["jwt","oidc","discovery","jwks","http-status","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"}