PrefectHQ/fastmcp · error · IdentityAssertionError
Malformed assertion payload: {e}
Error message
Malformed assertion payload: {e} What it means
The assertion's payload (claims) segment could not be decoded as a JSON object: base64/JSON decode failed or the required keys were absent while extracting claims unverified. FastMCP must read `iss` from the payload to select a trusted issuer before verifying the signature.
Source
Thrown at fastmcp_slim/fastmcp/server/auth/identity_assertion.py:368
# 1. typ header MUST be oauth-id-jag+jwt (SEP-990 §5.1).
try:
header = decode_jwt_header(assertion)
except (ValueError, KeyError, IndexError) as e:
raise IdentityAssertionError(f"Malformed assertion header: {e}") from e
if not isinstance(header, dict):
# A JSON-array/scalar header is valid JSON but not a JOSE header;
# guard before .get() so this maps to invalid_grant, not a 500.
raise IdentityAssertionError("Assertion JOSE header must be a JSON object")
if header.get("typ") != ID_JAG_TYP:
raise IdentityAssertionError(
f"Assertion typ must be {ID_JAG_TYP!r}, got {header.get('typ')!r}"
)
# 2. iss must be a trusted issuer before we fetch any keys for it.
try:
unverified_claims = _decode_unverified_claims(assertion)
except (ValueError, KeyError, IndexError) as e:
raise IdentityAssertionError(f"Malformed assertion payload: {e}") from e
if not isinstance(unverified_claims, dict):
raise IdentityAssertionError("Assertion payload is not a JSON object")
iss = unverified_claims.get("iss")
if not iss or iss not in self.config.trusted_issuers:
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")View on GitHub (pinned to 1f02114297)
Solutions
- Regenerate the assertion client-side with a standard JWT library and resend; check for truncation in transport (header size limits, proxy rewriting).
- Confirm the client sends the complete id-jag assertion, not a fragment or a different token.
- Decode the payload locally (jwt.io or PyJWT) to see what is malformed.
- Check middleware/proxies that might rewrite or truncate Authorization/body fields.
Example fix
// before: re-encoding the token assertion = base64.b64encode(raw_token).decode() // after: pass the JWT string as-is assertion = raw_token.decode() # original 'header.payload.signature' string
Defensive patterns
Strategy: validation
Validate before calling
import base64, json
def payload_parses(t: str) -> bool:
try:
json.loads(base64.urlsafe_b64decode(t.split('.')[1] + '=='))
return True
except Exception:
return False Type guard
def has_valid_payload_segment(token: str) -> bool:
import base64, json
parts = token.split('.')
if len(parts) != 3:
return False
try:
return isinstance(json.loads(base64.urlsafe_b64decode(parts[1] + '==')), dict)
except Exception:
return False Try / catch
try:
await provider.validate(assertion)
except IdentityAssertionError as e:
if 'Malformed assertion payload' in str(e):
log.warning('assertion payload unreadable; request fresh token from client')
raise Prevention
- Avoid token transformations in transit (no re-encoding, splitting, trimming)
- Check proxies/middleware for rewriting token bodies
- Request a fresh assertion when payloads fail to decode
- Round-trip test tokens through your exact transport
When it happens
Trigger: validate() receives an assertion whose second dot-delimited segment is not valid base64url JSON, or _decode_unverified_claims raises ValueError/KeyError/IndexError.
Common situations: Truncated or corrupted tokens in transit; the wrong token type passed as the assertion; tokens mangled by logging/serialization round-trips; hand-built test tokens.
Understand the failure class
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- Malformed assertion header: {e}
- Failed to extract key ID from token: {e}
- Assertion JOSE header must be a JSON object
- Assertion payload is not a JSON object
- Invalid client_assertion_type: expected {JWT_BEARER_ASSERTIO
AI-assisted analysis of PrefectHQ/fastmcp@1f02114297 (2026-08-29).
Data as JSON: /api/errors/a1a4751bd4893880.
Report an issue: GitHub.