{"record":{"id":"44aeb95dbf34d819","repo":"PrefectHQ/fastmcp","slug":"assertion-iat-is-in-the-future-44aeb9","errorCode":null,"errorMessage":"Assertion iat is in the future","messagePattern":"Assertion iat is in the future","errorType":"validation","errorClass":"IdentityAssertionError","httpStatus":null,"severity":"error","filePath":"fastmcp_slim/fastmcp/server/auth/identity_assertion.py","lineNumber":394,"sourceCode":"        verifier = await self._get_verifier(iss)\n        access_token = await verifier.load_access_token(assertion)\n        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","sourceCodeStart":376,"sourceCodeEnd":412,"githubUrl":"https://github.com/PrefectHQ/fastmcp/blob/1f021142978e0861cd910c8df4e8074bc7cf3978/fastmcp_slim/fastmcp/server/auth/identity_assertion.py#L376-L412","documentation":"The assertion JWT's `iat` (issued-at) claim is in the future by more than `CLOCK_SKEW_SECONDS` beyond the server's clock. The middleware rejects assertions whose issue time hasn't occurred yet, guarding against forged or badly timestamped tokens (RFC 7523 sanity requirements).","triggerScenarios":"Calling `validate()` with an assertion where `iat > now + CLOCK_SKEW_SECONDS` — client machine clock set ahead, iat accidentally generated from a wrong timezone-affected date computation, or iat encoded in milliseconds (huge numeric value interpreted as far-future).","commonSituations":"Developer laptop with drifted clock minting assertions locally; custom minting code computing iat via `datetime.now(timezone.utc).timestamp()` on a machine with wrong date; ms/seconds unit mixups.","solutions":["Synchronize the assertion-issuing machine's clock (NTP) with the server.","Ensure the issuer sets `iat = int(time.time())` at signing time, not a scheduled/offset time.","Confirm iat is epoch seconds; if the value is ~13 digits, convert from milliseconds.","If legitimate skew is small and recurring, the server operator can increase `CLOCK_SKEW_SECONDS` in the config."],"exampleFix":"// before\nclaims = {\"iat\": int(datetime(2026, 9, 1, tzinfo=timezone.utc).timestamp()), \"exp\": now + 300}\n// after\nclaims = {\"iat\": int(time.time()), \"exp\": int(time.time()) + 300}","handlingStrategy":"validation","validationCode":"import time\n\ndef iat_is_valid(claims: dict, skew: float = 60) -> bool:\n    iat = claims.get(\"iat\")\n    return iat is None or (isinstance(iat, (int, float)) and iat <= time.time() + skew)","typeGuard":"def is_epoch_seconds(v) -> bool:\n    return isinstance(v, (int, float)) and not isinstance(v, bool) and v < 10_000_000_000","tryCatchPattern":"try:\n    token = await exchange(assertion)\nexcept IdentityAssertionError as e:\n    if \"iat is in the future\" in str(e):\n        assertion = mint_assertion()  # re-mint with server-consistent timestamp\n        token = await exchange(assertion)\n    else:\n        raise","preventionTips":["Generate iat at signing time with int(time.time()) in UTC.","Keep minting machines NTP-synchronized with the server.","Avoid timezone-aware date math when computing iat; use epoch helpers."],"tags":["auth","jwt","clock-skew","identity-assertion"],"backgroundTag":"jwt-issued-in-future","analyzedSha":"1f021142978e0861cd910c8df4e8074bc7cf3978","analyzedAt":"2026-08-29T14:31:16.082Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}