PrefectHQ/fastmcp · error · IdentityAssertionError
OIDC discovery for issuer {issuer!r} failed: {e}
Error message
OIDC discovery for issuer {issuer!r} failed: {e} What it means
FastMCP's identity assertion provider fetches the OIDC discovery document for a trusted issuer and wraps any httpx transport/HTTP error or JSON parse failure into IdentityAssertionError. This maps the failure to an OAuth invalid_grant response instead of a 500, and records a failure timestamp so repeated attempts are throttled.
Source
Thrown at fastmcp_slim/fastmcp/server/auth/identity_assertion.py:288
failed_at is not None
and time.monotonic() - failed_at < self._discovery_failure_cooldown
):
raise IdentityAssertionError(
f"OIDC discovery for issuer {issuer!r} recently failed; backing off"
)
return await self._fetch_discovery(issuer)
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:View on GitHub (pinned to 1f02114297)
Solutions
- Verify the issuer URL in trusted_issuers is exactly the IdP's issuer identifier and that <issuer>/.well-known/openid-configuration (or .well-known/oauth-authorization-server) resolves and returns 200 with JSON from the server host (curl it).
- Check network egress/DNS from the FastMCP server (proxies, firewalls, VPC rules).
- Check the IdP status; if it is temporarily down, retry after the recorded discovery-failure backoff window elapses.
- If the discovery endpoint cannot return JSON, pin the JWKS URI via explicit provider configuration instead of relying on discovery.
Example fix
// before
config = IdentityAssertionConfig(trusted_issuers={"https://idp.example.com/auth"})
// after (verify the discovery URL returns JSON first, or pin jwks_uri)
config = IdentityAssertionConfig(
trusted_issuers={"https://idp.example.com"}, # matches IdP issuer exactly
# or supply explicit jwks_uri so discovery is not needed
) Defensive patterns
Strategy: retry
Validate before calling
import httpx
url = issuer.rstrip('/') + '/.well-known/openid-configuration'
async with httpx.AsyncClient() as c:
r = await c.get(url, timeout=10.0)
r.raise_for_status()
assert isinstance(r.json(), dict) Type guard
def is_valid_discovery(body) -> bool:
return isinstance(body, dict) and isinstance(body.get('jwks_uri'), str) and bool(body['jwks_uri']) Try / catch
from fastmcp.server.auth.identity_assertion import IdentityAssertionError
try:
await provider.validate(assertion)
except IdentityAssertionError as e:
if 'discovery' in str(e):
# transient IdP/network issue: back off and retry later
await asyncio.sleep(backoff)
else:
raise Prevention
- Curl the discovery URL from the server host during deployment smoke tests
- Pin jwks_uri explicitly if discovery reliability is a concern
- Monitor IdP status and configure alerting on repeated discovery failures
- Use https and exact issuer identifiers in trusted_issuers
When it happens
Trigger: Calling validate() on an id-jag assertion whose issuer's discovery URL (issuer + well-known path) is unreachable, returns a non-2xx status (raise_for_status), or returns a body that is not valid JSON (response.json() raises ValueError).
Common situations: Misconfigured trusted issuer URL (typo, http vs https, missing path); IdP downtime or firewall egress blocking the server's outbound request to the IdP; discovery endpoint returning HTML error pages instead of JSON; DNS failures in containerized deployments.
Related errors
- OIDC discovery for issuer {issuer!r} recently failed; backin
- Unexpected authorization response: {response.status_code}
- str(e)
- OIDC discovery document for issuer {issuer!r} is not a JSON
- OIDC discovery document for issuer {issuer!r} has no jwks_ur
AI-assisted analysis of PrefectHQ/fastmcp@1f02114297 (2026-08-29).
Data as JSON: /api/errors/f4650b18906e974f.
Report an issue: GitHub.