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

  1. Regenerate the assertion client-side with a standard JWT library and resend; check for truncation in transport (header size limits, proxy rewriting).
  2. Confirm the client sends the complete id-jag assertion, not a fragment or a different token.
  3. Decode the payload locally (jwt.io or PyJWT) to see what is malformed.
  4. 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

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

Related errors


AI-assisted analysis of PrefectHQ/fastmcp@1f02114297 (2026-08-29). Data as JSON: /api/errors/a1a4751bd4893880. Report an issue: GitHub.