PrefectHQ/fastmcp · error · ValueError

identity_assertion.trusted_issuers must not be empty

Error message

identity_assertion.trusted_issuers must not be empty

What it means

The `trusted_issuers` field on the identity_assertion settings must be a non-empty list; a pydantic `field_validator` raises this ValueError at configuration load time when the list is empty. Trusted issuers are mandatory because assertion verification is entirely driven by the issuer/JWKS list.

Source

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

            "issuer string (mirroring `jwks_uris`). Issuers absent here fall "
            "back to `algorithm`."
        ),
    )
    access_token_expiry_seconds: int = Field(
        default=300,
        gt=0,
        description=(
            "Lifetime, in seconds, of the short-lived access token minted from an "
            "ID-JAG. SEP-990 relies on the client re-exchanging a fresh assertion, so "
            "this is intentionally short and no refresh token is issued."
        ),
    )

    @field_validator("trusted_issuers")
    @classmethod
    def _validate_trusted_issuers(cls, v: list[str]) -> list[str]:
        if not v:
            raise ValueError("identity_assertion.trusted_issuers must not be empty")
        for issuer in v:
            if not issuer or not issuer.strip():
                raise ValueError("trusted_issuers entries must be non-empty strings")
        return v

    @field_validator("algorithm")
    @classmethod
    def _validate_algorithm(cls, v: str | None) -> str | None:
        # Trusted issuers are verified via JWKS (public keys only), so the
        # algorithm must be one of the asymmetric JWS algorithms JWTVerifier
        # actually supports — HS* (shared-secret) has no JWKS equivalent, and
        # anything else (EdDSA, or a typo like RS999) would otherwise surface
        # as a 500 on the first exchange instead of a clean config error now.
        if v is not None and v not in SUPPORTED_ASSERTION_ALGORITHMS:
            supported = ", ".join(sorted(SUPPORTED_ASSERTION_ALGORITHMS))
            raise ValueError(
                f"Unsupported algorithm {v!r} for identity assertion: trusted "
                f"issuers are verified via JWKS, so algorithm must be one of "

View on GitHub (pinned to 1f02114297)

Solutions

  1. Add at least one trusted issuer URL to the `trusted_issuers` list in your identity assertion settings
  2. Fix the env var / config parsing so the list is populated (check separators, quoting)
  3. If no issuers should be trusted, disable the identity assertion feature entirely rather than passing an empty list

Example fix

# before
IdentityAssertionSettings(trusted_issuers=[])
# after
IdentityAssertionSettings(trusted_issuers=["https://accounts.google.com"])
Defensive patterns

Strategy: validation

Validate before calling

issuers = [u.strip() for u in os.environ.get("TRUSTED_ISSUERS", "").split(",") if u.strip()]
if not issuers:
    raise ValueError("trusted_issuers must contain at least one issuer URL")

Type guard

def has_trusted_issuers(cfg: dict) -> bool:
    v = cfg.get("trusted_issuers")
    return isinstance(v, list) and len(v) > 0

Try / catch

try:
    settings = IdentityAssertionSettings(**cfg)
except ValidationError as e:
    logger.error("identity_assertion config invalid: %s", e)
    raise SystemExit(1)

Prevention

When it happens

Trigger: Configuring identity assertion settings with `trusted_issuers=[]` (or constructing the settings model with an empty list) — validation fails during model instantiation, not at request time.

Common situations: Environment/config file that yields an empty list (e.g. an empty `TRUSTED_ISSUERS` env var split into `[]`); YAML/JSON config with `trusted_issuers: []`; code that builds the settings conditionally and never populates the list.

Related errors


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