{"record":{"id":"f3ac2b2b6629ad7c","repo":"PrefectHQ/fastmcp","slug":"assertion-typ-must-be-id-jag-typ-r-got-header","errorCode":null,"errorMessage":"Assertion typ must be {ID_JAG_TYP!r}, got {header.get('typ')!r}","messagePattern":"Assertion typ must be (.+?), got (.+?)","errorType":"exception","errorClass":"IdentityAssertionError","httpStatus":null,"severity":"error","filePath":"fastmcp_slim/fastmcp/server/auth/identity_assertion.py","lineNumber":360,"sourceCode":"        Returns:\n            The verified claims (including `sub`, `iss`, and any `resource`/`scope`).\n\n        Raises:\n            IdentityAssertionError: If the assertion is invalid for any reason.\n        \"\"\"\n        self._maybe_cleanup()\n\n        # 1. typ header MUST be oauth-id-jag+jwt (SEP-990 §5.1).\n        try:\n            header = decode_jwt_header(assertion)\n        except (ValueError, KeyError, IndexError) as e:\n            raise IdentityAssertionError(f\"Malformed assertion header: {e}\") from e\n        if not isinstance(header, dict):\n            # A JSON-array/scalar header is valid JSON but not a JOSE header;\n            # guard before .get() so this maps to invalid_grant, not a 500.\n            raise IdentityAssertionError(\"Assertion JOSE header must be a JSON object\")\n        if header.get(\"typ\") != ID_JAG_TYP:\n            raise IdentityAssertionError(\n                f\"Assertion typ must be {ID_JAG_TYP!r}, got {header.get('typ')!r}\"\n            )\n\n        # 2. iss must be a trusted issuer before we fetch any keys for it.\n        try:\n            unverified_claims = _decode_unverified_claims(assertion)\n        except (ValueError, KeyError, IndexError) as e:\n            raise IdentityAssertionError(f\"Malformed assertion payload: {e}\") from e\n        if not isinstance(unverified_claims, dict):\n            raise IdentityAssertionError(\"Assertion payload is not a JSON object\")\n        iss = unverified_claims.get(\"iss\")\n        if not iss or iss not in self.config.trusted_issuers:\n            raise IdentityAssertionError(f\"Untrusted assertion issuer: {iss!r}\")\n\n        # 3. Verify signature, iss, aud, and exp via JWTVerifier.\n        verifier = await self._get_verifier(iss)\n        access_token = await verifier.load_access_token(assertion)\n        if access_token is None:","sourceCodeStart":342,"sourceCodeEnd":378,"githubUrl":"https://github.com/PrefectHQ/fastmcp/blob/1f021142978e0861cd910c8df4e8074bc7cf3978/fastmcp_slim/fastmcp/server/auth/identity_assertion.py#L342-L378","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Ensure the client performs the id-jag exchange and sends the resulting assertion (typ oauth-id-jag+jwt), not the access token.","Check the token-minting code or library sets header typ='oauth-id-jag+jwt' when creating the assertion.","Upgrade the client SDK/IdP integration if it predates SEP-990 typ requirements.","Inspect the header with a JWT decoder to confirm typ before debugging server-side."],"exampleFix":"// before\nheader = {\"alg\": \"RS256\", \"typ\": \"JWT\"}\n// after\nheader = {\"alg\": \"RS256\", \"typ\": \"oauth-id-jag+jwt\"}","handlingStrategy":"validation","validationCode":"import base64, json\nh = json.loads(base64.urlsafe_b64decode(assertion.split('.')[0] + '=='))\nassert h.get('typ') == 'oauth-id-jag+jwt', f\"bad typ: {h.get('typ')!r}\"","typeGuard":"def has_idjag_typ(token: str) -> bool:\n    import base64, json\n    h = json.loads(base64.urlsafe_b64decode(token.split('.')[0] + '=='))\n    return isinstance(h, dict) and h.get('typ') == 'oauth-id-jag+jwt'","tryCatchPattern":"try:\n    await provider.validate(assertion)\nexcept IdentityAssertionError as e:\n    if 'typ must be' in str(e):\n        log.warning('wrong token type sent as assertion (typ=%s)', e)\n    raise","preventionTips":["Ensure clients send the id-jag assertion, not an access token","Set typ='oauth-id-jag+jwt' in the minting header per SEP-990","Keep client SDKs current with the spec","Inspect token typ during client debugging"],"tags":["jwt","typ","oidc","spec-compliance"],"backgroundTag":"jwt-typ-mismatch","analyzedSha":"1f021142978e0861cd910c8df4e8074bc7cf3978","analyzedAt":"2026-08-29T14:31:16.082Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}