PrefectHQ/fastmcp · error · AuthenticationError
Invalid client assertion: {e}
Error message
Invalid client assertion: {e} What it means
This AuthenticationError wraps a ValueError from the CIMD manager's validate_private_key_jwt. The client sent a JWT assertion for private_key_jwt auth, but the JWT failed validation — bad signature, wrong audience, expired, malformed, or signed by a key not in the client's published jwks. The original ValueError is chained as __cause__ for debugging.
Source
Thrown at fastmcp_slim/fastmcp/server/auth/auth.py:292
assertion = form_data.get("client_assertion")
if assertion_type != JWT_BEARER_ASSERTION_TYPE:
raise AuthenticationError(
f"Invalid client_assertion_type: expected {JWT_BEARER_ASSERTION_TYPE}"
)
if not assertion or not isinstance(assertion, str):
raise AuthenticationError("Missing client_assertion")
# Validate the JWT assertion using CIMD manager
try:
await self._cimd_manager.validate_private_key_jwt(
assertion=assertion,
client=client,
token_endpoint=self._token_endpoint_url,
)
except ValueError as e:
raise AuthenticationError(f"Invalid client assertion: {e}") from e
return client
# Delegate to SDK for other authentication methods
return await super().authenticate_request(request)
class AuthProvider(TokenVerifierProtocol):
"""Base class for all FastMCP authentication providers.
This class provides a unified interface for all authentication providers,
whether they are simple token verifiers or full OAuth authorization servers.
All providers must be able to verify tokens and can optionally provide
custom authentication routes.
"""
def __init__(
self,View on GitHub (pinned to 1f02114297)
Solutions
- Inspect the chained ValueError (e.__cause__) to identify the exact failure: signature, audience, exp, or format.
- Sign the assertion with the private key matching a key published in the client's CIMD jwks, using an asymmetric algorithm (RS256/ES256).
- Set the JWT 'aud' claim to the server's token endpoint URL exactly as advertised in server metadata.
- Issue a fresh assertion with iat/exp within the accepted window and check for clock skew (NTP).
- Update the hosted CIMD document if the client's signing keys changed.
Example fix
// before (wrong audience)
claims = {'iss': client_id, 'sub': client_id, 'aud': server_base_url, 'exp': exp}
// after
claims = {'iss': client_id, 'sub': client_id, 'aud': token_endpoint_url, 'exp': exp} Defensive patterns
Strategy: try-catch
Validate before calling
import time
claims = {'iss': client_id, 'sub': client_id, 'aud': token_endpoint_url, 'iat': int(time.time()), 'exp': int(time.time()) + 300}
assert claims['aud'] == token_endpoint_url
assert claims['exp'] > time.time() Try / catch
try:
client = await auth.authenticate_request(request)
except AuthenticationError as e:
logger.warning('private_key_jwt rejected: %s', e.__cause__)
return JSONResponse({'error': 'invalid_client'}, status_code=401) Prevention
- Sign assertions with the private key matching a jwks entry in the hosted CIMD document.
- Set aud exactly to the server's token endpoint URL, not the base URL.
- Keep iat/exp within a few minutes and sync clocks (NTP) to avoid skew rejections.
- Rotate client keys by first updating the CIMD jwks, then switching signing keys.
- Use asymmetric algorithms (RS256/ES256) supported by joserfc.
When it happens
Trigger: POST to the token endpoint with client_assertion present for a private_key_jwt CIMD client, but the JWT has an invalid signature against the client's jwks, targets the wrong audience (not the token endpoint URL), is expired or issued in the future, uses an unsupported algorithm, or is structurally malformed.
Common situations: Clock skew between client and server making the JWT appear expired; the client rotated keys but the CIMD jwks still lists old keys; audience set to the server base URL instead of the token endpoint; signing with HS256 instead of an asymmetric algorithm; assertion reused after exp.
Related errors
- CIMD document must have jwks_uri or jwks for private_key_jwt
- Invalid JWT assertion
- Invalid client_assertion_type: expected {JWT_BEARER_ASSERTIO
- Missing client_assertion
- Unsupported JWK key type: {key_type!r}
AI-assisted analysis of PrefectHQ/fastmcp@1f02114297 (2026-08-29).
Data as JSON: /api/errors/54947bd7dc9f2179.
Report an issue: GitHub.