PrefectHQ/fastmcp · error · IdentityAssertionError

Assertion lifetime too long (max {self.MAX_ASSERTION_LIFETIM

Error message

Assertion lifetime too long (max {self.MAX_ASSERTION_LIFETIME}s)

What it means

When the assertion carries `iat`, the middleware caps the assertion lifetime: `exp - iat` must not exceed `MAX_ASSERTION_LIFETIME` seconds. Identity assertions are meant to be short-lived, single-use credentials (RFC 7523); a long-lived assertion is rejected because it broadens the replay/theft window.

Source

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

        if access_token is None:
            raise IdentityAssertionError(
                "Assertion failed signature/issuer/audience/expiry validation"
            )
        claims = access_token.claims

        now = time.time()
        exp = _numeric_date_claim(claims, "exp")
        iat = _numeric_date_claim(claims, "iat")
        nbf = _numeric_date_claim(claims, "nbf")
        if exp is None:
            raise IdentityAssertionError("Assertion must include exp claim")
        if nbf is not None and nbf > now + self.CLOCK_SKEW_SECONDS:
            raise IdentityAssertionError("Assertion is not yet valid (nbf in future)")
        if iat is not None:
            if iat > now + self.CLOCK_SKEW_SECONDS:
                raise IdentityAssertionError("Assertion iat is in the future")
            if exp - iat > self.MAX_ASSERTION_LIFETIME:
                raise IdentityAssertionError(
                    f"Assertion lifetime too long (max {self.MAX_ASSERTION_LIFETIME}s)"
                )
        elif exp > now + self.MAX_ASSERTION_LIFETIME:
            raise IdentityAssertionError(
                f"Assertion exp too far in future (max {self.MAX_ASSERTION_LIFETIME}s)"
            )

        # 4. sub is mandatory (RFC 7523 §3) — it identifies the end user.
        sub = claims.get("sub")
        if not sub:
            raise IdentityAssertionError("Assertion must include sub claim")

        # 5. Required scopes on the issued access token derive from the assertion.
        if self.config.required_scopes:
            granted = set(_assertion_scopes(claims))
            missing = set(self.config.required_scopes) - granted
            if missing:
                raise IdentityAssertionError(

View on GitHub (pinned to 1f02114297)

Solutions

  1. Mint assertions with a short lifetime (e.g. `exp = iat + 60..300` seconds) well under MAX_ASSERTION_LIFETIME.
  2. If the use case genuinely needs longer windows, the server operator can raise `MAX_ASSERTION_LIFETIME` in the identity assertion config.
  3. Check issuer SDK defaults for token TTL and override them for the assertion endpoint.
  4. Decode the JWT and compute exp-iat to confirm the actual lifetime being minted.

Example fix

// before
claims = {"iat": now, "exp": now + 86400}
// after
claims = {"iat": now, "exp": now + 300}
Defensive patterns

Strategy: validation

Validate before calling

def lifetime_is_short(claims: dict, max_lifetime: int = 300) -> bool:
    iat, exp = claims.get("iat"), claims.get("exp")
    return isinstance(iat, (int, float)) and isinstance(exp, (int, float)) and exp - iat <= max_lifetime

Type guard

def has_short_lifetime(claims, max_lifetime=300) -> bool:
    return isinstance(claims.get("iat"), (int, float)) and isinstance(claims.get("exp"), (int, float))

Try / catch

try:
    token = await exchange(assertion)
except IdentityAssertionError as e:
    if "lifetime too long" in str(e):
        assertion = mint_assertion(lifetime=300)
        token = await exchange(assertion)
    else:
        raise

Prevention

When it happens

Trigger: Calling `validate()` with an assertion where `exp - iat > MAX_ASSERTION_LIFETIME` — e.g. an issuer minting an assertion with a 24-hour or multi-day expiry instead of a few minutes.

Common situations: Reusing an existing access-token TTL (hours) for the ID-JAG assertion; a template JWT with default long expiry adapted to assertions; confusion between access-token lifetimes and assertion lifetimes.

Related errors


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