{"record":{"id":"fa03884d9f11fe08","repo":"PrefectHQ/fastmcp","slug":"assertion-lifetime-too-long-exp-iat-s-max-se","errorCode":null,"errorMessage":"Assertion lifetime too long: {exp - iat}s (max {self.MAX_ASSERTION_LIFETIME}s)","messagePattern":"Assertion lifetime too long: (.+?)s \\(max (.+?)s\\)","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"fastmcp_slim/fastmcp/server/auth/cimd.py","lineNumber":606,"sourceCode":"\n        # 3. Validate assertion lifetime (exp and iat)\n        now = time.time()\n        exp = claims.get(\"exp\")\n        iat = claims.get(\"iat\")\n\n        if not exp:\n            raise ValueError(\"Assertion must include exp claim\")\n\n        # Validate exp is in the future (with small clock skew tolerance)\n        if exp < now - 30:  # 30 second clock skew tolerance\n            raise ValueError(\"Assertion has expired\")\n\n        # If iat is present, validate it and check assertion lifetime\n        if iat:\n            if iat > now + 30:  # 30 second clock skew tolerance\n                raise ValueError(\"Assertion iat is in the future\")\n            if exp - iat > self.MAX_ASSERTION_LIFETIME:\n                raise ValueError(\n                    f\"Assertion lifetime too long: {exp - iat}s (max {self.MAX_ASSERTION_LIFETIME}s)\"\n                )\n        else:\n            # No iat, enforce max lifetime from now\n            if exp > now + self.MAX_ASSERTION_LIFETIME:\n                raise ValueError(\n                    f\"Assertion exp too far in future (max {self.MAX_ASSERTION_LIFETIME}s)\"\n                )\n\n        # 4. Additional RFC 7523 validation: sub claim must equal client_id\n        if claims.get(\"sub\") != client_id:\n            raise ValueError(f\"Assertion sub claim must be {client_id}\")\n\n        # 5. Check jti for replay attacks (RFC 7523 requirement)\n        jti = claims.get(\"jti\")\n        if not jti:\n            raise ValueError(\"Assertion must include jti claim\")\n","sourceCodeStart":588,"sourceCodeEnd":624,"githubUrl":"https://github.com/PrefectHQ/fastmcp/blob/1f021142978e0861cd910c8df4e8074bc7cf3978/fastmcp_slim/fastmcp/server/auth/cimd.py#L588-L624","documentation":"Raised by validate_assertion when the span between 'iat' and 'exp' exceeds the validator's MAX_ASSERTION_LIFETIME. The library caps how long a single client assertion may remain valid to limit replay exposure.","triggerScenarios":"Minting an assertion with exp - iat greater than MAX_ASSERTION_LIFETIME (e.g. setting exp = now + 3600 when the server allows far less); reusing an organization-wide template assertion with a 1-hour lifetime.","commonSituations":"Copy-pasted JWT-minting code from other OAuth providers with different (longer) lifetime rules; hardcoding exp offsets that were valid against other servers.","solutions":["Reduce the assertion lifetime: set exp = iat + a short value within MAX_ASSERTION_LIFETIME","Check the validator's configured MAX_ASSERTION_LIFETIME and match it","Mint per-request assertions with ~5-minute lifetimes instead of hour-long ones"],"exampleFix":"// before\npayload[\"exp\"] = payload[\"iat\"] + 3600\n// after\npayload[\"exp\"] = payload[\"iat\"] + 300","handlingStrategy":"validation","validationCode":"import time\nMAX_LIFETIME = 300\nclaims = jwt.decode(token, options={\"verify_signature\": False})\nif claims.get(\"iat\") and claims[\"exp\"] - claims[\"iat\"] > MAX_LIFETIME:\n    raise ValueError(\"assertion lifetime exceeds server cap; shorten exp\")","typeGuard":"def lifetime_ok(claims: dict, max_lifetime: int) -> bool:\n    iat, exp = claims.get(\"iat\"), claims.get(\"exp\")\n    return not (isinstance(iat, (int, float)) and isinstance(exp, (int, float))) or \\\n        exp - iat <= max_lifetime","tryCatchPattern":"try:\n    validator.validate_assertion(token, client_id, jwks)\nexcept ValueError as e:\n    if \"lifetime too long\" in str(e):\n        token = mint_assertion(client_id, lifetime=300)\n    else:\n        raise","preventionTips":["Match your assertion lifetime to the server's MAX_ASSERTION_LIFETIME","Default to 5-minute lifetimes for client assertions","Centralize assertion minting so lifetime policy lives in one place"],"tags":["oauth","jwt","private-key-jwt","validation"],"backgroundTag":"jwt-lifetime-too-long","analyzedSha":"1f021142978e0861cd910c8df4e8074bc7cf3978","analyzedAt":"2026-08-29T14:31:16.082Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}