PrefectHQ/fastmcp · error · IdentityAssertionError
Assertion missing required scopes: {sorted(missing)}
Error message
Assertion missing required scopes: {sorted(missing)} What it means
The identity assertion config declares `required_scopes`, and the assertion's scope claims (scope/scope arrays via `_assertion_scopes`) do not grant all of them. The middleware derives the issued token's scopes from the assertion, so a missing required scope means the request cannot be authorized and is rejected with the sorted list of missing scopes.
Source
Thrown at fastmcp_slim/fastmcp/server/auth/identity_assertion.py:414
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(
f"Assertion client_id {assertion_client_id!r} does not match "
f"authenticated client {client_id!r}"
)
if resource_url is not None:
assertion_resource = claims.get("resource")
if not isinstance(assertion_resource, str) or not assertion_resource:
raise IdentityAssertionError("Assertion is missing resource claim")View on GitHub (pinned to 1f02114297)
Solutions
- Update the issuer/IdP client configuration to grant all required scopes in the assertion.
- Have the user re-authenticate/re-consent so the new scopes appear in issued assertions.
- Align scope names exactly between config.required_scopes and what the issuer emits.
- As a last resort, the server operator can trim `required_scopes` in the config to what the issuer legitimately provides.
Example fix
// before
claims = {"scope": "read", ...} # server requires ["read", "write"]
// after
claims = {"scope": "read write", ...} Defensive patterns
Strategy: validation
Validate before calling
def scopes_satisfied(claims: dict, required: set[str]) -> bool:
granted = set()
scope = claims.get("scope") or claims.get("scopes") or []
if isinstance(scope, str):
granted = set(scope.split())
elif isinstance(scope, list):
granted = set(scope)
return required.issubset(granted) Type guard
def grants_scopes(claims: dict, required: set[str]) -> bool:
s = claims.get("scope", "")
return isinstance(s, str) and required.issubset(set(s.split())) Try / catch
try:
token = await exchange(assertion)
except IdentityAssertionError as e:
if "missing required scopes" in str(e):
assertion = await obtain_assertion(request_scopes=REQUIRED_SCOPES) # re-consent flow
token = await exchange(assertion)
else:
raise Prevention
- Keep required_scopes in the server config and the IdP client's granted scopes in sync.
- Re-authenticate after the server widens required_scopes.
- Log granted vs required scopes during integration testing.
When it happens
Trigger: Calling `validate()` with an assertion whose granted scope set is a strict subset of `config.required_scopes` — e.g. config requires ["read", "write"] but the assertion only carries "read".
Common situations: Server tightened `required_scopes` in the IdentityAssertionConfig without updating the issuing IdP's client scopes; user consent at the IdP omitted a scope; scope naming mismatches (e.g. `read:data` vs `read`).
Related errors
- Assertion must include exp claim
- Assertion must include sub claim
- Assertion client_id {assertion_client_id!r} does not match a
- Assertion is missing resource claim
- Assertion must include a string jti claim
AI-assisted analysis of PrefectHQ/fastmcp@1f02114297 (2026-08-29).
Data as JSON: /api/errors/3de1249e62356c9e.
Report an issue: GitHub.