BerriAI/litellm · error · Exception

JWT Auth: Failed to parse OIDC discovery document at {url}:

Error message

JWT Auth: Failed to parse OIDC discovery document at {url}: {e}

What it means

Raised in _resolve_jwks_url when the OIDC discovery endpoint returned HTTP 200 but response.json() failed - the body is not valid JSON. The original parse error is chained into the message. This means the URL resolved and answered, but what it returned is not a discovery document.

Source

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

        """
        if ".well-known/openid-configuration" not in url:
            return url

        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

View on GitHub (pinned to 77b7c6c40c)

Solutions

  1. curl -s <url> | python -m json.tool from the proxy host to see the raw body and where JSON parsing fails
  2. If HTML comes back, the URL is being intercepted - use the true IdP discovery URL or the JWKS URL directly
  3. Ensure the fetch is unauthenticated (no auth wall) or whitelist the proxy's egress for that host

Example fix

# before: URL that returns an HTML login page with 200
JWT_PUBLIC_KEY_URL=https://sso.example.com/.well-known/openid-configuration

# after: the IdP's real discovery endpoint (or direct JWKS URL)
JWT_PUBLIC_KEY_URL=https://idp.example.com/oidc/realms/prod/.well-known/openid-configuration
Defensive patterns

Strategy: validation

Validate before calling

import httpx

async def discovery_returns_json(url: str) -> bool:
    async with httpx.AsyncClient() as c:
        r = await c.get(url)
        if r.status_code != 200:
            return False
        try:
            r.json()
            return True
        except ValueError:
            return False  # HTML login/block page served with 200

Prevention

When it happens

Trigger: JWT_PUBLIC_KEY_URL contains .well-known/openid-configuration and the server responds 200 with HTML (a login page, an SPA shell, a proxy block page) or truncated/invalid JSON, so json decoding raises.

Common situations: A gateway/SSO portal in front of the IdP serving an HTML page for unauthenticated fetchers; a typo'd URL that matches some catch-all route returning 200 HTML; response corruption through a corporate proxy.

Understand the failure class

Background: "Invalid JSON response" and "Failed to parse response" errors: when an API answers 200 but the body isn't the JSON your library expected — this error's family across 28 libraries.

Related errors


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