PrefectHQ/fastmcp · critical · ValueError
jwt_signing_key is required when upstream_client_secret is n
Error message
jwt_signing_key is required when upstream_client_secret is not provided. The JWT signing key cannot be derived without a client secret.
What it means
OAuthProxy requires a JWT signing key to sign issued tokens. If no explicit jwt_signing_key is given, it is derived deterministically from upstream_client_secret; with neither provided, derivation is impossible and __init__ raises ValueError. This fails fast at startup rather than producing unsigned or randomly-keyed tokens.
Source
Thrown at fastmcp_slim/fastmcp/server/auth/oauth_proxy/proxy.py:559
self._extra_token_params: dict[str, str] = extra_token_params or {}
# Token expiry fallback (None means use smart default based on refresh token)
self._fallback_access_token_expiry_seconds: int | None = (
fallback_access_token_expiry_seconds
)
self._fallback_refresh_token_expiry_seconds: int = (
fallback_refresh_token_expiry_seconds
if fallback_refresh_token_expiry_seconds is not None
else DEFAULT_REFRESH_TOKEN_EXPIRY_SECONDS
)
self._fastmcp_access_token_expiry_seconds: int | None = (
fastmcp_access_token_expiry_seconds
)
self._token_expiry_threshold_seconds: int = token_expiry_threshold_seconds
if jwt_signing_key is None:
if upstream_client_secret is None:
raise ValueError(
"jwt_signing_key is required when upstream_client_secret is not provided. "
"The JWT signing key cannot be derived without a client secret."
)
jwt_signing_key = derive_jwt_key(
high_entropy_material=upstream_client_secret,
salt="fastmcp-jwt-signing-key",
)
if isinstance(jwt_signing_key, str):
if len(jwt_signing_key) < 12:
logger.warning(
"jwt_signing_key is less than 12 characters; it is recommended to use a longer. "
"string for the key derivation."
)
jwt_signing_key = derive_jwt_key(
low_entropy_material=jwt_signing_key,
salt="fastmcp-jwt-signing-key",
)View on GitHub (pinned to 1f02114297)
Solutions
- Pass an explicit jwt_signing_key (e.g. a stable high-entropy secret loaded from an environment variable)
- Provide upstream_client_secret so the key can be derived with derive_jwt_key
- If the upstream flow has no secret, generate and persist a dedicated signing key instead of leaving both None
Example fix
// before
proxy = OAuthProxy(upstream_authorization_endpoint=..., upstream_token_endpoint=...) # no secret, no key
// after
proxy = OAuthProxy(
upstream_authorization_endpoint=...,
upstream_token_endpoint=...,
jwt_signing_key=os.environ["JWT_SIGNING_KEY"],
) Defensive patterns
Strategy: validation
Validate before calling
jwt_key = os.environ.get("FASTMCP_JWT_SIGNING_KEY")
client_secret = os.environ.get("UPSTREAM_CLIENT_SECRET")
if jwt_key is None and client_secret is None:
raise SystemExit("Set FASTMCP_JWT_SIGNING_KEY or UPSTREAM_CLIENT_SECRET before starting") Type guard
def has_signing_material(key: str | None, secret: str | None) -> bool:
return key is not None or secret is not None Try / catch
try:
proxy = OAuthProxy(..., jwt_signing_key=key, upstream_client_secret=secret)
except ValueError as e:
if "jwt_signing_key is required" in str(e):
raise SystemExit("Configuration error: provide jwt_signing_key or upstream_client_secret") from e
raise Prevention
- Load signing material from environment variables with startup-time validation
- Always pass an explicit jwt_signing_key when the upstream flow uses no client secret
- Fail fast in deployment config checks before the app boots
When it happens
Trigger: Constructing OAuthProxy with jwt_signing_key=None and upstream_client_secret=None (or omitted).
Common situations: Configuring the proxy against an IdP flow that doesn't use a client secret (e.g. public-client/PKCE setups) and forgetting to supply a signing key; refactoring constructor arguments and dropping the secret; missing env var not passed into the constructor.
Related errors
- OAuth provider has no server URL. Either pass mcp_url to OAu
- OAuth server rejected the static client credentials. Verify
- Invalid client_assertion_type: expected {JWT_BEARER_ASSERTIO
- Missing client_assertion
- Invalid client assertion: {e}
AI-assisted analysis of PrefectHQ/fastmcp@1f02114297 (2026-08-29).
Data as JSON: /api/errors/fe5f027102027057.
Report an issue: GitHub.