{"record":{"id":"977a3fb24b350f67","repo":"PrefectHQ/fastmcp","slug":"either-client-secret-or-jwt-signing-key-must-be-pr","errorCode":null,"errorMessage":"Either client_secret or jwt_signing_key must be provided. jwt_signing_key is required when client_secret is omitted (e.g., for PKCE public clients).","messagePattern":"Either client_secret or jwt_signing_key must be provided\\. jwt_signing_key is required when client_secret is omitted \\(e\\.g\\., for PKCE public clients\\)\\.","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"fastmcp_slim/fastmcp/server/auth/oidc_proxy.py","lineNumber":356,"sourceCode":"                MCP clients can't refresh gracefully (e.g. `mcp-remote`).\n            token_expiry_threshold_seconds: Number of seconds before actual expiry to consider\n                a token as expired (default 0). Prevents race conditions where a token\n                passes the expiry check but expires before the next operation completes.\n            enable_cimd: Whether to enable CIMD (Client ID Metadata Document) client support.\n                When True, clients can use their metadata document URL as client_id instead of\n                Dynamic Client Registration. Default is True.\n            identity_assertion: Optional SEP-990 identity assertion (ID-JAG) configuration.\n                When provided, the token endpoint accepts the RFC 7523 jwt-bearer grant\n                carrying an ID-JAG issued by one of the configured trusted issuers.\n        \"\"\"\n        if not config_url:\n            raise ValueError(\"Missing required config URL\")\n\n        if not client_id:\n            raise ValueError(\"Missing required client id\")\n\n        if not client_secret and not jwt_signing_key:\n            raise ValueError(\n                \"Either client_secret or jwt_signing_key must be provided. \"\n                \"jwt_signing_key is required when client_secret is omitted \"\n                \"(e.g., for PKCE public clients).\"\n            )\n\n        if not base_url:\n            raise ValueError(\"Missing required base URL\")\n\n        # Validate that verifier-specific parameters are not used with custom verifier\n        if token_verifier is not None:\n            if algorithm is not None:\n                raise ValueError(\n                    \"Cannot specify 'algorithm' when providing a custom token_verifier. \"\n                    \"Configure the algorithm on your token verifier instead.\"\n                )\n            if required_scopes is not None:\n                raise ValueError(\n                    \"Cannot specify 'required_scopes' when providing a custom token_verifier. \"","sourceCodeStart":338,"sourceCodeEnd":374,"githubUrl":"https://github.com/PrefectHQ/fastmcp/blob/1f021142978e0861cd910c8df4e8074bc7cf3978/fastmcp_slim/fastmcp/server/auth/oidc_proxy.py#L338-L374","documentation":"OIDCProxy requires either a client_secret (confidential client) or a jwt_signing_key (for secret-less flows such as PKCE public clients or private_key_jwt) and raises ValueError if neither is provided.","triggerScenarios":"Constructing OIDCProxy(...) with client_secret omitted/empty AND jwt_signing_key omitted — e.g. setting up a public client but forgetting jwt_signing_key, or a secret env var that is empty string (falsy).","commonSituations":"PKCE public-client setups missing the signing key; deployed without the client secret mounted; empty-string secrets from placeholder env values that fail the truthiness check.","solutions":["Provide client_secret for a confidential client, or provide jwt_signing_key if the client is public/PKCE or uses JWT-based auth","Verify the secret env var is non-empty (empty strings are treated as missing)","If intentional secret-less flow, generate/load the jwt_signing_key before construction"],"exampleFix":"// before\nproxy = OIDCProxy(config_url=..., client_id=\"app\")  # neither secret nor key\n// after\nproxy = OIDCProxy(config_url=..., client_id=\"app\", client_secret=os.environ[\"OIDC_CLIENT_SECRET\"])","handlingStrategy":"validation","validationCode":"if not client_secret and not jwt_signing_key:\n    raise ValueError(\"provide client_secret (confidential) or jwt_signing_key (public/PKCE)\")","typeGuard":null,"tryCatchPattern":"try:\n    proxy = OIDCProxy(config_url=..., client_id=..., client_secret=secret, jwt_signing_key=key)\nexcept ValueError as e:\n    logger.error(\"credential configuration invalid: %s\", e)\n    raise SystemExit(1)","preventionTips":["Decide confidential-vs-public client up front and configure exactly one credential path","Treat empty-string env values as missing (strip and check)","Document in your deploy checklist: secret or signing key is mandatory"],"tags":["oidc","configuration","client-secret"],"backgroundTag":"missing-client-secret","analyzedSha":"1f021142978e0861cd910c8df4e8074bc7cf3978","analyzedAt":"2026-08-29T14:31:16.082Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}