{"record":{"id":"c01f71552a63559c","repo":"PrefectHQ/fastmcp","slug":"oidc-discovery-document-for-issuer-issuer-r-is-n","errorCode":null,"errorMessage":"OIDC discovery document for issuer {issuer!r} is not a JSON object","messagePattern":"OIDC discovery document for issuer (.+?) is not a JSON object","errorType":"exception","errorClass":"IdentityAssertionError","httpStatus":null,"severity":"error","filePath":"fastmcp_slim/fastmcp/server/auth/identity_assertion.py","lineNumber":295,"sourceCode":"\n    async def _fetch_discovery(self, issuer: str) -> str:\n        \"\"\"Perform the actual discovery fetch; caller holds the issuer lock.\"\"\"\n        config_url = issuer.rstrip(\"/\") + \"/.well-known/openid-configuration\"\n        try:\n            async with httpx2.AsyncClient() as client:\n                response = await client.get(config_url, timeout=10.0)\n                response.raise_for_status()\n                body = response.json()\n        except (httpx2.HTTPError, ValueError) as e:\n            self._discovery_failures[issuer] = time.monotonic()\n            raise IdentityAssertionError(\n                f\"OIDC discovery for issuer {issuer!r} failed: {e}\"\n            ) from e\n        if not isinstance(body, dict):\n            # Valid JSON that isn't an object (e.g. `[]` or a bare string) —\n            # guard before .get() so a misbehaving discovery endpoint maps to\n            # invalid_grant, not a 500 on every subsequent exchange.\n            raise IdentityAssertionError(\n                f\"OIDC discovery document for issuer {issuer!r} is not a JSON object\"\n            )\n\n        jwks_uri = body.get(\"jwks_uri\")\n        if not jwks_uri or not isinstance(jwks_uri, str):\n            raise IdentityAssertionError(\n                f\"OIDC discovery document for issuer {issuer!r} has no jwks_uri\"\n            )\n        return jwks_uri\n\n    async def _get_verifier(self, issuer: str) -> JWTVerifier:\n        from fastmcp.server.auth.providers.jwt import JWTVerifier as _JWTVerifier\n\n        verifier = self._verifiers.get(issuer)\n        if verifier is not None:\n            return verifier\n\n        jwks_uri = (self.config.jwks_uris or {}).get(issuer)","sourceCodeStart":277,"sourceCodeEnd":313,"githubUrl":"https://github.com/PrefectHQ/fastmcp/blob/1f021142978e0861cd910c8df4e8074bc7cf3978/fastmcp_slim/fastmcp/server/auth/identity_assertion.py#L277-L313","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["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.","Confirm you are hitting the OIDC discovery endpoint, not some other endpoint (wrong well-known path configured in the issuer URL).","If the IdP is broken and cannot be fixed, configure the verifier with an explicit JWKS URI instead of discovery.","Check for transparent proxies that replace 404 bodies with JSON error payloads, and fix proxy routing."],"exampleFix":"// before (bad discovery response)\n[]\n// after (correct discovery response shape)\n{\"issuer\": \"https://idp.example.com\", \"jwks_uri\": \"https://idp.example.com/.well-known/jwks.json\"}","handlingStrategy":"validation","validationCode":"import json, httpx\nbody = httpx.get(issuer + '/.well-known/openid-configuration').json()\nif not isinstance(body, dict):\n    raise ValueError('discovery document is not a JSON object')","typeGuard":"def is_dict(x) -> bool:\n    return isinstance(x, dict)","tryCatchPattern":"try:\n    await provider.validate(assertion)\nexcept IdentityAssertionError as e:\n    if 'not a JSON object' in str(e):\n        log.error('IdP discovery doc malformed; check IdP metadata endpoint')\n    raise","preventionTips":["Verify discovery output with jq during IdP setup","Beware proxies that replace error pages with JSON strings","Use well-known, compliant OIDC providers","Add an integration test fetching discovery at startup"],"tags":["oidc","discovery","json","schema"],"backgroundTag":"oidc-discovery-invalid-response","analyzedSha":"1f021142978e0861cd910c8df4e8074bc7cf3978","analyzedAt":"2026-08-29T14:31:16.082Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}