{"record":{"id":"54947bd7dc9f2179","repo":"PrefectHQ/fastmcp","slug":"invalid-client-assertion-e","errorCode":null,"errorMessage":"Invalid client assertion: {e}","messagePattern":"Invalid client assertion: (.+?)","errorType":"http","errorClass":"AuthenticationError","httpStatus":401,"severity":"error","filePath":"fastmcp_slim/fastmcp/server/auth/auth.py","lineNumber":292,"sourceCode":"            assertion = form_data.get(\"client_assertion\")\n\n            if assertion_type != JWT_BEARER_ASSERTION_TYPE:\n                raise AuthenticationError(\n                    f\"Invalid client_assertion_type: expected {JWT_BEARER_ASSERTION_TYPE}\"\n                )\n\n            if not assertion or not isinstance(assertion, str):\n                raise AuthenticationError(\"Missing client_assertion\")\n\n            # Validate the JWT assertion using CIMD manager\n            try:\n                await self._cimd_manager.validate_private_key_jwt(\n                    assertion=assertion,\n                    client=client,\n                    token_endpoint=self._token_endpoint_url,\n                )\n            except ValueError as e:\n                raise AuthenticationError(f\"Invalid client assertion: {e}\") from e\n\n            return client\n\n        # Delegate to SDK for other authentication methods\n        return await super().authenticate_request(request)\n\n\nclass AuthProvider(TokenVerifierProtocol):\n    \"\"\"Base class for all FastMCP authentication providers.\n\n    This class provides a unified interface for all authentication providers,\n    whether they are simple token verifiers or full OAuth authorization servers.\n    All providers must be able to verify tokens and can optionally provide\n    custom authentication routes.\n    \"\"\"\n\n    def __init__(\n        self,","sourceCodeStart":274,"sourceCodeEnd":310,"githubUrl":"https://github.com/PrefectHQ/fastmcp/blob/1f021142978e0861cd910c8df4e8074bc7cf3978/fastmcp_slim/fastmcp/server/auth/auth.py#L274-L310","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before (wrong audience)\nclaims = {'iss': client_id, 'sub': client_id, 'aud': server_base_url, 'exp': exp}\n// after\nclaims = {'iss': client_id, 'sub': client_id, 'aud': token_endpoint_url, 'exp': exp}","handlingStrategy":"try-catch","validationCode":"import time\nclaims = {'iss': client_id, 'sub': client_id, 'aud': token_endpoint_url, 'iat': int(time.time()), 'exp': int(time.time()) + 300}\nassert claims['aud'] == token_endpoint_url\nassert claims['exp'] > time.time()","typeGuard":null,"tryCatchPattern":"try:\n    client = await auth.authenticate_request(request)\nexcept AuthenticationError as e:\n    logger.warning('private_key_jwt rejected: %s', e.__cause__)\n    return JSONResponse({'error': 'invalid_client'}, status_code=401)","preventionTips":["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."],"tags":["oauth","jwt","signature-validation","cimd"],"backgroundTag":"jwt-validation-failed","analyzedSha":"1f021142978e0861cd910c8df4e8074bc7cf3978","analyzedAt":"2026-08-29T14:31:16.082Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}