PrefectHQ/fastmcp · error · IdentityAssertionError

Assertion typ must be {ID_JAG_TYP!r}, got {header.get('typ')

Error message

Assertion typ must be {ID_JAG_TYP!r}, got {header.get('typ')!r}

What it means

The assertion header is a valid JSON object but its `typ` claim does not equal the required id-jag type marker `oauth-id-jag+jwt` (SEP-990 §5.1). FastMCP enforces this so ordinary access tokens or other JWTs cannot be used as identity assertions.

Source

Thrown at fastmcp_slim/fastmcp/server/auth/identity_assertion.py:360

        Returns:
            The verified claims (including `sub`, `iss`, and any `resource`/`scope`).

        Raises:
            IdentityAssertionError: If the assertion is invalid for any reason.
        """
        self._maybe_cleanup()

        # 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:

View on GitHub (pinned to 1f02114297)

Solutions

  1. Ensure the client performs the id-jag exchange and sends the resulting assertion (typ oauth-id-jag+jwt), not the access token.
  2. Check the token-minting code or library sets header typ='oauth-id-jag+jwt' when creating the assertion.
  3. Upgrade the client SDK/IdP integration if it predates SEP-990 typ requirements.
  4. Inspect the header with a JWT decoder to confirm typ before debugging server-side.

Example fix

// before
header = {"alg": "RS256", "typ": "JWT"}
// after
header = {"alg": "RS256", "typ": "oauth-id-jag+jwt"}
Defensive patterns

Strategy: validation

Validate before calling

import base64, json
h = json.loads(base64.urlsafe_b64decode(assertion.split('.')[0] + '=='))
assert h.get('typ') == 'oauth-id-jag+jwt', f"bad typ: {h.get('typ')!r}"

Type guard

def has_idjag_typ(token: str) -> bool:
    import base64, json
    h = json.loads(base64.urlsafe_b64decode(token.split('.')[0] + '=='))
    return isinstance(h, dict) and h.get('typ') == 'oauth-id-jag+jwt'

Try / catch

try:
    await provider.validate(assertion)
except IdentityAssertionError as e:
    if 'typ must be' in str(e):
        log.warning('wrong token type sent as assertion (typ=%s)', e)
    raise

Prevention

When it happens

Trigger: validate() receives an assertion whose header dict's typ is missing or is anything other than 'oauth-id-jag+jwt' — e.g. 'JWT', 'at+jwt', or absent.

Common situations: Client accidentally sends a regular access token or ID token instead of the identity assertion; an IdP or client SDK version that stamps typ=JWT; manual token construction omitting the typ header.

Related errors


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