BerriAI/litellm · error · Exception

JWT Auth: OIDC discovery document at {url} does not contain

Error message

JWT Auth: OIDC discovery document at {url} does not contain a 'jwks_uri' field.

What it means

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.

Source

Thrown at litellm/proxy/auth/handle_jwt.py:632

        cache_key: Final = f"litellm_oidc_discovery_{url}"
        cached_jwks_uri: Final = await self.user_api_key_cache.async_get_cache(cache_key)
        if cached_jwks_uri is not None:
            return cached_jwks_uri

        verbose_proxy_logger.debug("JWT Auth: Fetching OIDC discovery document from %s", url)
        response: Final = await self.http_handler.get(url)
        if response.status_code != 200:
            raise Exception(
                f"JWT Auth: OIDC discovery endpoint {url} returned status {response.status_code}: {response.text}"
            )
        try:
            discovery: Final = response.json()
        except Exception as e:
            raise Exception(f"JWT Auth: Failed to parse OIDC discovery document at {url}: {e}")

        jwks_uri: Final = discovery.get("jwks_uri")
        if not jwks_uri:
            raise Exception(f"JWT Auth: OIDC discovery document at {url} does not contain a 'jwks_uri' field.")

        verbose_proxy_logger.debug("JWT Auth: Resolved OIDC discovery %s -> jwks_uri=%s", url, jwks_uri)
        await self.user_api_key_cache.async_set_cache(
            key=cache_key,
            value=jwks_uri,
            ttl=self._get_public_key_cache_ttl(),
        )
        return jwks_uri

    def _get_public_key_cache_ttl(self) -> float:
        litellm_jwtauth: Final = getattr(self, "litellm_jwtauth", None)
        if litellm_jwtauth is None:
            return 600
        return litellm_jwtauth.public_key_ttl

    async def _get_public_key_from_jwks_url(self, jwks_url: str, kid: str | None) -> dict:
        resolved_jwks_url: Final = await self._resolve_jwks_url(jwks_url)
        cache_key: Final = f"litellm_jwt_auth_keys_{resolved_jwks_url}"

View on GitHub (pinned to 77b7c6c40c)

Solutions

  1. 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
  2. If you want discovery, curl the URL and confirm the JSON top level includes jwks_uri
  3. Fix the IdP/gateway routing so the well-known path serves the genuine discovery document

Example fix

# before: JWKS URL mislabeled with the discovery substring, triggering discovery parsing
JWT_PUBLIC_KEY_URL=https://idp.example.com/jwks/.well-known/openid-configuration-certs

# after: plain JWKS URL - discovery code path is skipped entirely
JWT_PUBLIC_KEY_URL=https://idp.example.com/oidc/jwks
Defensive patterns

Strategy: validation

Validate before calling

import httpx

async def discovery_doc_is_valid(url: str) -> bool:
    async with httpx.AsyncClient() as c:
        doc = (await c.get(url)).json()
    return isinstance(doc, dict) and bool(doc.get("jwks_uri"))

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of BerriAI/litellm@77b7c6c40c (2026-08-18). Data as JSON: /api/errors/d68ed6fac3a03bb6. Report an issue: GitHub.