PrefectHQ/fastmcp · error · IdentityAssertionError
Assertion must include sub claim
Error message
Assertion must include sub claim
What it means
RFC 7523 §3 makes `sub` (subject) mandatory: it identifies the end user on whose behalf the assertion is presented. FastMCP's identity assertion validator raises this error when the verified JWT claims lack a `sub` or it is empty, since a subject-less assertion cannot yield an identity for the issued access token.
Source
Thrown at fastmcp_slim/fastmcp/server/auth/identity_assertion.py:407
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.
if self.config.required_scopes:
granted = set(_assertion_scopes(claims))
missing = set(self.config.required_scopes) - granted
if missing:
raise IdentityAssertionError(
f"Assertion missing required scopes: {sorted(missing)}"
)
# 6. The signed client_id and resource claims bind the assertion to the
# presenting client and this server. Checked here — before jti is
# recorded as consumed below — so an assertion presented with the
# wrong binding is rejected without burning replay protection for
# whichever client/server it actually belongs to.
assertion_client_id = claims.get("client_id")
if not assertion_client_id or assertion_client_id != client_id:
raise IdentityAssertionError(View on GitHub (pinned to 1f02114297)
Solutions
- Configure the issuer to include the end user's identifier as `sub` in the assertion payload.
- If minting assertions in code, add `sub: <user_id>` before signing.
- Check IdP claim-mapping/transform rules that may strip or rename sub (e.g. mapped to user_id).
- Decode the JWT and confirm `sub` is a non-empty string.
Example fix
// before
claims = {"iss": iss, "aud": aud, "exp": now + 300, "jti": jti}
// after
claims = {"iss": iss, "sub": user_id, "aud": aud, "exp": now + 300, "jti": jti} Defensive patterns
Strategy: validation
Validate before calling
def has_sub(claims: dict) -> bool:
sub = claims.get("sub")
return isinstance(sub, str) and bool(sub) Type guard
def has_nonempty_str(claims: dict, key: str) -> bool:
v = claims.get(key)
return isinstance(v, str) and bool(v) Try / catch
try:
token = await exchange(assertion)
except IdentityAssertionError as e:
if "sub claim" in str(e):
raise ValueError("Issuer misconfiguration: assertions lack a subject") from e
raise Prevention
- Make sub a mandatory field in your assertion minting helper's signature.
- Audit IdP claim-mapping rules for strips/renames of sub.
- Add a unit test asserting every minted assertion has a non-empty sub.
When it happens
Trigger: Calling `validate()` with an assertion whose payload has no `sub` claim, `sub: null`, or `sub: ""` — typically from an issuer configured for machine-only tokens or a custom minting function that forgot the subject.
Common situations: Service-to-service token templates that omit sub; custom assertion minters copying only iss/aud/exp; IdPs issuing anonymous or pre-auth tokens repurposed as identity assertions.
Related errors
- Assertion must include exp claim
- Assertion must include a string jti claim
- Assertion is not yet valid (nbf in future)
- Assertion iat is in the future
- Assertion lifetime too long (max {self.MAX_ASSERTION_LIFETIM
AI-assisted analysis of PrefectHQ/fastmcp@1f02114297 (2026-08-29).
Data as JSON: /api/errors/4108c0657bdd7a48.
Report an issue: GitHub.