{"record":{"id":"1a7dcde85b4b3348","repo":"PrefectHQ/fastmcp","slug":"assertion-lifetime-too-long-max-self-max-asserti","errorCode":null,"errorMessage":"Assertion lifetime too long (max {self.MAX_ASSERTION_LIFETIME}s)","messagePattern":"Assertion lifetime too long \\(max (.+?)s\\)","errorType":"validation","errorClass":"IdentityAssertionError","httpStatus":null,"severity":"error","filePath":"fastmcp_slim/fastmcp/server/auth/identity_assertion.py","lineNumber":396,"sourceCode":"        if access_token is None:\n            raise IdentityAssertionError(\n                \"Assertion failed signature/issuer/audience/expiry validation\"\n            )\n        claims = access_token.claims\n\n        now = time.time()\n        exp = _numeric_date_claim(claims, \"exp\")\n        iat = _numeric_date_claim(claims, \"iat\")\n        nbf = _numeric_date_claim(claims, \"nbf\")\n        if exp is None:\n            raise IdentityAssertionError(\"Assertion must include exp claim\")\n        if nbf is not None and nbf > now + self.CLOCK_SKEW_SECONDS:\n            raise IdentityAssertionError(\"Assertion is not yet valid (nbf in future)\")\n        if iat is not None:\n            if iat > now + self.CLOCK_SKEW_SECONDS:\n                raise IdentityAssertionError(\"Assertion iat is in the future\")\n            if exp - iat > self.MAX_ASSERTION_LIFETIME:\n                raise IdentityAssertionError(\n                    f\"Assertion lifetime too long (max {self.MAX_ASSERTION_LIFETIME}s)\"\n                )\n        elif exp > now + self.MAX_ASSERTION_LIFETIME:\n            raise IdentityAssertionError(\n                f\"Assertion exp too far in future (max {self.MAX_ASSERTION_LIFETIME}s)\"\n            )\n\n        # 4. sub is mandatory (RFC 7523 §3) — it identifies the end user.\n        sub = claims.get(\"sub\")\n        if not sub:\n            raise IdentityAssertionError(\"Assertion must include sub claim\")\n\n        # 5. Required scopes on the issued access token derive from the assertion.\n        if self.config.required_scopes:\n            granted = set(_assertion_scopes(claims))\n            missing = set(self.config.required_scopes) - granted\n            if missing:\n                raise IdentityAssertionError(","sourceCodeStart":378,"sourceCodeEnd":414,"githubUrl":"https://github.com/PrefectHQ/fastmcp/blob/1f021142978e0861cd910c8df4e8074bc7cf3978/fastmcp_slim/fastmcp/server/auth/identity_assertion.py#L378-L414","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Mint assertions with a short lifetime (e.g. `exp = iat + 60..300` seconds) well under MAX_ASSERTION_LIFETIME.","If the use case genuinely needs longer windows, the server operator can raise `MAX_ASSERTION_LIFETIME` in the identity assertion config.","Check issuer SDK defaults for token TTL and override them for the assertion endpoint.","Decode the JWT and compute exp-iat to confirm the actual lifetime being minted."],"exampleFix":"// before\nclaims = {\"iat\": now, \"exp\": now + 86400}\n// after\nclaims = {\"iat\": now, \"exp\": now + 300}","handlingStrategy":"validation","validationCode":"def lifetime_is_short(claims: dict, max_lifetime: int = 300) -> bool:\n    iat, exp = claims.get(\"iat\"), claims.get(\"exp\")\n    return isinstance(iat, (int, float)) and isinstance(exp, (int, float)) and exp - iat <= max_lifetime","typeGuard":"def has_short_lifetime(claims, max_lifetime=300) -> bool:\n    return isinstance(claims.get(\"iat\"), (int, float)) and isinstance(claims.get(\"exp\"), (int, float))","tryCatchPattern":"try:\n    token = await exchange(assertion)\nexcept IdentityAssertionError as e:\n    if \"lifetime too long\" in str(e):\n        assertion = mint_assertion(lifetime=300)\n        token = await exchange(assertion)\n    else:\n        raise","preventionTips":["Use a minting helper with a fixed short default lifetime (e.g. 5 minutes).","Never reuse access-token TTLs for ID-JAG assertions.","Document the server's MAX_ASSERTION_LIFETIME with the issuer's owners."],"tags":["auth","jwt","token-lifetime","identity-assertion"],"backgroundTag":"jwt-lifetime-too-long","analyzedSha":"1f021142978e0861cd910c8df4e8074bc7cf3978","analyzedAt":"2026-08-29T14:31:16.082Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}