PrefectHQ/fastmcp · error · IdentityAssertionError

OIDC discovery document for issuer {issuer!r} has no jwks_ur

Error message

OIDC discovery document for issuer {issuer!r} has no jwks_uri

What it means

The discovery document is a JSON object but contains no usable `jwks_uri` string field, which FastMCP requires to fetch the issuer's signing keys. Without it no JWT verifier can be built, so validation fails with invalid_grant.

Source

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

                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)
        if not jwks_uri:
            jwks_uri = await self._discover_jwks_uri(issuer)

        algorithm = (self.config.algorithms or {}).get(issuer, self.config.algorithm)
        verifier = _JWTVerifier(
            jwks_uri=jwks_uri,

View on GitHub (pinned to 1f02114297)

Solutions

  1. Fix the IdP metadata to include a valid string jwks_uri pointing at the JWKS endpoint (verify with curl).
  2. Verify with curl .../.well-known/openid-configuration | jq .jwks_uri that it returns a non-empty string URL.
  3. If the IdP cannot publish jwks_uri, configure FastMCP's JWT verifier with the JWKS URI explicitly rather than relying on discovery.
  4. Ensure the issuer URL in trusted_issuers points at the real OIDC provider, not a different server that happens to serve some JSON.

Example fix

// before
{"issuer": "https://idp.example.com"}
// after
{"issuer": "https://idp.example.com", "jwks_uri": "https://idp.example.com/.well-known/jwks.json"}
Defensive patterns

Strategy: validation

Validate before calling

doc = httpx.get(issuer + '/.well-known/openid-configuration').json()
jwks_uri = doc.get('jwks_uri')
assert isinstance(jwks_uri, str) and jwks_uri.startswith('https://')

Type guard

def has_jwks_uri(doc: dict) -> bool:
    u = doc.get('jwks_uri')
    return isinstance(u, str) and bool(u)

Try / catch

try:
    await provider.validate(assertion)
except IdentityAssertionError as e:
    if 'no jwks_uri' in str(e):
        log.error('IdP metadata missing jwks_uri; configure explicit JWKS')
    raise

Prevention

When it happens

Trigger: validate() on an assertion whose issuer's discovery document has jwks_uri missing, empty, null, or a non-string JSON type (number/list/object).

Common situations: Minimal/broken OIDC implementations that omit jwks_uri from metadata; IdPs where JWKS is served at a nonstandard location and metadata was never configured; partially deployed IdP staging environments.

Related errors


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