PrefectHQ/fastmcp · error · IdentityAssertionError
Assertion is not yet valid (nbf in future)
Error message
Assertion is not yet valid (nbf in future)
What it means
The assertion JWT's `nbf` (not-before) claim is further in the future than the server's current time plus the allowed clock-skew allowance (`CLOCK_SKEW_SECONDS`). The middleware rejects assertions that are not yet valid, per RFC 7523, so clients cannot pre-mint assertions for future use beyond the skew tolerance.
Source
Thrown at fastmcp_slim/fastmcp/server/auth/identity_assertion.py:391
raise IdentityAssertionError(f"Untrusted assertion issuer: {iss!r}")
# 3. Verify signature, iss, aud, and exp via JWTVerifier.
verifier = await self._get_verifier(iss)
access_token = await verifier.load_access_token(assertion)
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.View on GitHub (pinned to 1f02114297)
Solutions
- Fix the issuer to set `nbf` to the current time (or omit it entirely — nbf is optional).
- Resynchronize the machine generating the assertion (NTP) if the clock is ahead of the server.
- Verify nbf units are epoch seconds, not milliseconds; divide by 1000 if the issuer emits ms.
- If modest skew is expected operationally, the server operator can raise `CLOCK_SKEW_SECONDS` on the identity assertion config.
Example fix
// before
claims = {"exp": now + 300, "nbf": now + 3600}
// after
claims = {"exp": now + 300, "nbf": now} Defensive patterns
Strategy: validation
Validate before calling
import time
def nbf_is_valid(claims: dict, skew: float = 60) -> bool:
nbf = claims.get("nbf")
return nbf is None or (isinstance(nbf, (int, float)) and nbf <= time.time() + skew) Type guard
def is_epoch_seconds(v) -> bool:
return isinstance(v, (int, float)) and not isinstance(v, bool) and v < 10_000_000_000 Try / catch
try:
token = await exchange(assertion)
except IdentityAssertionError as e:
if "not yet valid" in str(e):
time.sleep(2) # small skew, then retry once with a fresh assertion
assertion = mint_assertion()
token = await exchange(assertion)
else:
raise Prevention
- Omit nbf entirely — it is optional for assertions.
- Sync clocks via NTP on machines minting assertions.
- Emit epoch seconds, never milliseconds, for nbf.
When it happens
Trigger: Calling `validate()` with an assertion whose `nbf` exceeds `now + CLOCK_SKEW_SECONDS` — e.g. a client clock running fast, an issuer setting `nbf` to a future timestamp, or an issuer writing `nbf` in milliseconds instead of epoch seconds (values like 1.7e12 read as ~year 56000).
Common situations: Clock skew between client machine and server (VM resumed, NTP drift); issuer SDK misconfigured with a future validity window; a misencoded nbf (milliseconds vs seconds) from a custom minter.
Related errors
- Assertion iat is in the future
- Assertion must include exp claim
- Assertion lifetime too long (max {self.MAX_ASSERTION_LIFETIM
- Assertion exp too far in future (max {self.MAX_ASSERTION_LIF
- Assertion must include sub claim
AI-assisted analysis of PrefectHQ/fastmcp@1f02114297 (2026-08-29).
Data as JSON: /api/errors/9d29b674968b8156.
Report an issue: GitHub.