{"record":{"id":"67ad28650f6da290","repo":"PrefectHQ/fastmcp","slug":"assertion-iat-is-in-the-future","errorCode":null,"errorMessage":"Assertion iat is in the future","messagePattern":"Assertion iat is in the future","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"fastmcp_slim/fastmcp/server/auth/cimd.py","lineNumber":604,"sourceCode":"\n        claims = access_token.claims\n\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:","sourceCodeStart":586,"sourceCodeEnd":622,"githubUrl":"https://github.com/PrefectHQ/fastmcp/blob/1f021142978e0861cd910c8df4e8074bc7cf3978/fastmcp_slim/fastmcp/server/auth/cimd.py#L586-L622","documentation":"Raised by validate_assertion when the assertion's 'iat' (issued-at) claim is more than 30 seconds in the future relative to the server clock. A future iat means the assertion was minted on a machine whose clock runs ahead, so the server treats it as not-yet-valid/suspicious.","triggerScenarios":"Token issuer host has a clock more than 30s ahead of the CIMD validator host; manual construction of iat using milliseconds instead of seconds (iat = now_ms/1000 mistake variants like int(time.time()*1000) misused).","commonSituations":"Distributed deployments without NTP; local dev asserting against a remote server with skewed clocks; unit tests that hardcode future timestamps.","solutions":["Sync clocks via NTP on the assertion-signing machine","Compute iat as int(time.time()) (seconds, not milliseconds) when minting","Remove any hardcoded/future-dated iat in test fixtures"],"exampleFix":"// before\niat = int(time.time() * 1000)  # milliseconds\n// after\niat = int(time.time())  # seconds","handlingStrategy":"validation","validationCode":"import time\nclaims = jwt.decode(token, options={\"verify_signature\": False})\niat = claims.get(\"iat\")\nif iat is not None and iat > time.time() + 30:\n    raise ValueError(\"iat is in the future; check clock sync / timestamp units\")","typeGuard":"def iat_not_future(claims: dict, skew: float = 30) -> bool:\n    iat = claims.get(\"iat\")\n    return not isinstance(iat, (int, float)) or iat <= time.time() + skew","tryCatchPattern":"try:\n    validator.validate_assertion(token, client_id, jwks)\nexcept ValueError as e:\n    if \"iat is in the future\" in str(e):\n        sync_clock_and_remint()\n    else:\n        raise","preventionTips":["Use int(time.time()) (seconds) for iat, never milliseconds","Keep clocks NTP-synced across services","Never hardcode future timestamps in fixtures"],"tags":["oauth","jwt","clock-skew","validation"],"backgroundTag":"jwt-clock-skew","analyzedSha":"1f021142978e0861cd910c8df4e8074bc7cf3978","analyzedAt":"2026-08-29T14:31:16.082Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}