PrefectHQ/fastmcp · error · IdentityAssertionError
Assertion JOSE header must be a JSON object
Error message
Assertion JOSE header must be a JSON object
What it means
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.
Source
Thrown at fastmcp_slim/fastmcp/server/auth/identity_assertion.py:358
the assertion's signed `resource` claim, for the same reason.
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)View on GitHub (pinned to 1f02114297)
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.
Example fix
// before (invalid header payload)
["alg","HS256"]
// after
{"alg": "RS256", "typ": "oauth-id-jag+jwt"} Defensive patterns
Strategy: type-guard
Validate before calling
import base64, json
header = json.loads(base64.urlsafe_b64decode(assertion.split('.')[0] + '=='))
assert isinstance(header, dict), 'JOSE header must be a JSON object' Type guard
def header_is_object(token: str) -> bool:
import base64, json
h = json.loads(base64.urlsafe_b64decode(token.split('.')[0] + '=='))
return isinstance(h, dict) Try / catch
try:
await provider.validate(assertion)
except IdentityAssertionError as e:
if 'JOSE header must be a JSON object' in str(e):
log.warning('assertion header is not a JSON object; re-mint token')
raise Prevention
- 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
When it happens
Trigger: validate() receives an assertion whose header segment base64-decodes to a JSON array or scalar (e.g. `[1,2]`, `"abc"`, `null`).
Common situations: Hand-crafted or fuzzer-generated tokens in tests; a broken token-minting implementation serializing the header incorrectly; corrupted tokens stored/tranformed by middleware.
Related errors
- Assertion payload is not a JSON object
- OIDC discovery document for issuer {issuer!r} is not a JSON
- Malformed assertion header: {e}
- Malformed assertion payload: {e}
- Invalid JWKS JSON: {e}
AI-assisted analysis of PrefectHQ/fastmcp@1f02114297 (2026-08-29).
Data as JSON: /api/errors/f559177f7c31c16f.
Report an issue: GitHub.