PrefectHQ/fastmcp · error · IdentityAssertionError

OIDC discovery document for issuer {issuer!r} is not a JSON

Error message

OIDC discovery document for issuer {issuer!r} is not a JSON object

What it means

The discovery endpoint returned a 200 response with valid JSON, but the parsed body is not a JSON object (e.g. an array, string, or number). FastMCP raises before calling .get() so a misbehaving IdP yields invalid_grant rather than an AttributeError/500 on every token exchange.

Source

Thrown at fastmcp_slim/fastmcp/server/auth/identity_assertion.py:295

    async def _fetch_discovery(self, issuer: str) -> str:
        """Perform the actual discovery fetch; caller holds the issuer lock."""
        config_url = issuer.rstrip("/") + "/.well-known/openid-configuration"
        try:
            async with httpx2.AsyncClient() as client:
                response = await client.get(config_url, timeout=10.0)
                response.raise_for_status()
                body = response.json()
        except (httpx2.HTTPError, ValueError) as e:
            self._discovery_failures[issuer] = time.monotonic()
            raise IdentityAssertionError(
                f"OIDC discovery for issuer {issuer!r} failed: {e}"
            ) from e
        if not isinstance(body, dict):
            # Valid JSON that isn't an object (e.g. `[]` or a bare string) —
            # guard before .get() so a misbehaving discovery endpoint maps to
            # invalid_grant, not a 500 on every subsequent exchange.
            raise IdentityAssertionError(
                f"OIDC discovery document for issuer {issuer!r} is not a JSON object"
            )

        jwks_uri = body.get("jwks_uri")
        if not jwks_uri or not isinstance(jwks_uri, str):
            raise IdentityAssertionError(
                f"OIDC discovery document for issuer {issuer!r} has no jwks_uri"
            )
        return jwks_uri

    async def _get_verifier(self, issuer: str) -> JWTVerifier:
        from fastmcp.server.auth.providers.jwt import JWTVerifier as _JWTVerifier

        verifier = self._verifiers.get(issuer)
        if verifier is not None:
            return verifier

        jwks_uri = (self.config.jwks_uris or {}).get(issuer)

View on GitHub (pinned to 1f02114297)

Solutions

  1. curl the issuer's discovery URL and inspect the raw body; fix the IdP/gateway so it returns a proper JSON object with issuer/jwks_uri fields.
  2. Confirm you are hitting the OIDC discovery endpoint, not some other endpoint (wrong well-known path configured in the issuer URL).
  3. If the IdP is broken and cannot be fixed, configure the verifier with an explicit JWKS URI instead of discovery.
  4. Check for transparent proxies that replace 404 bodies with JSON error payloads, and fix proxy routing.

Example fix

// before (bad discovery response)
[]
// after (correct discovery response shape)
{"issuer": "https://idp.example.com", "jwks_uri": "https://idp.example.com/.well-known/jwks.json"}
Defensive patterns

Strategy: validation

Validate before calling

import json, httpx
body = httpx.get(issuer + '/.well-known/openid-configuration').json()
if not isinstance(body, dict):
    raise ValueError('discovery document is not a JSON object')

Type guard

def is_dict(x) -> bool:
    return isinstance(x, dict)

Try / catch

try:
    await provider.validate(assertion)
except IdentityAssertionError as e:
    if 'not a JSON object' in str(e):
        log.error('IdP discovery doc malformed; check IdP metadata endpoint')
    raise

Prevention

When it happens

Trigger: validate() on an assertion whose issuer's discovery document parses to a non-dict JSON value — the guard `if not isinstance(body, dict)` fires right after response.json() succeeds.

Common situations: IdP behind a misconfigured gateway/proxy returning a JSON-encoded error string; a custom or buggy OIDC provider whose metadata endpoint emits `[]`; mocking/placeholder endpoints in staging.

Related errors


AI-assisted analysis of PrefectHQ/fastmcp@1f02114297 (2026-08-29). Data as JSON: /api/errors/c01f71552a63559c. Report an issue: GitHub.