{"record":{"id":"f559177f7c31c16f","repo":"PrefectHQ/fastmcp","slug":"assertion-jose-header-must-be-a-json-object","errorCode":null,"errorMessage":"Assertion JOSE header must be a JSON object","messagePattern":"Assertion JOSE header must be a JSON object","errorType":"exception","errorClass":"IdentityAssertionError","httpStatus":null,"severity":"error","filePath":"fastmcp_slim/fastmcp/server/auth/identity_assertion.py","lineNumber":358,"sourceCode":"                the assertion's signed `resource` claim, for the same reason.\n\n        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)","sourceCodeStart":340,"sourceCodeEnd":376,"githubUrl":"https://github.com/PrefectHQ/fastmcp/blob/1f021142978e0861cd910c8df4e8074bc7cf3978/fastmcp_slim/fastmcp/server/auth/identity_assertion.py#L340-L376","documentation":"The assertion's header decoded to valid JSON but is not a JSON object — a JOSE header must be an object for .get() checks to work. FastMCP guards this so a pathological token maps to invalid_grant instead of crashing with a 500.","triggerScenarios":"validate() receives an assertion whose header segment base64-decodes to a JSON array or scalar (e.g. `[1,2]`, `\"abc\"`, `null`).","commonSituations":"Hand-crafted or fuzzer-generated tokens in tests; a broken token-minting implementation serializing the header incorrectly; corrupted tokens stored/tranformed by middleware.","solutions":["Regenerate the assertion with a standard JWT library (the header must be a JSON object with alg/typ).","Inspect the header: base64url-decode the first segment and confirm it is `{...}`.","Fix any custom token-minting code that dumps a list or string as the header.","If tokens come from a third party, report the malformed token issue to that provider."],"exampleFix":"// before (invalid header payload)\n[\"alg\",\"HS256\"]\n// after\n{\"alg\": \"RS256\", \"typ\": \"oauth-id-jag+jwt\"}","handlingStrategy":"type-guard","validationCode":"import base64, json\nheader = json.loads(base64.urlsafe_b64decode(assertion.split('.')[0] + '=='))\nassert isinstance(header, dict), 'JOSE header must be a JSON object'","typeGuard":"def header_is_object(token: str) -> bool:\n    import base64, json\n    h = json.loads(base64.urlsafe_b64decode(token.split('.')[0] + '=='))\n    return isinstance(h, dict)","tryCatchPattern":"try:\n    await provider.validate(assertion)\nexcept IdentityAssertionError as e:\n    if 'JOSE header must be a JSON object' in str(e):\n        log.warning('assertion header is not a JSON object; re-mint token')\n    raise","preventionTips":["Mint tokens only with standard JWT libraries","Reject non-object headers at the client boundary","Fuzz-test custom token minters","Never hand-assemble JWT segments"],"tags":["jwt","malformed","json","schema"],"backgroundTag":"malformed-jwt-header","analyzedSha":"1f021142978e0861cd910c8df4e8074bc7cf3978","analyzedAt":"2026-08-29T14:31:16.082Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}