{"record":{"id":"37e1139fce44db5f","repo":"vercel/ai","slug":"client-secret-basic-authentication-requires-a-clie","errorCode":null,"errorMessage":"client_secret_basic authentication requires a client_secret","messagePattern":"client_secret_basic authentication requires a client_secret","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/mcp/src/tool/oauth.ts","lineNumber":839,"sourceCode":"      return;\n    case 'client_secret_post':\n      applyPostAuth(client_id, client_secret, params);\n      return;\n    case 'none':\n      applyPublicAuth(client_id, params);\n      return;\n    default:\n      throw new Error(`Unsupported client authentication method: ${method}`);\n  }\n}\n\nfunction applyBasicAuth(\n  clientId: string,\n  clientSecret: string | undefined,\n  headers: Headers,\n): void {\n  if (!clientSecret) {\n    throw new Error(\n      'client_secret_basic authentication requires a client_secret',\n    );\n  }\n\n  const credentials = btoa(`${clientId}:${clientSecret}`);\n  headers.set('Authorization', `Basic ${credentials}`);\n}\n\n/**\n * Applies POST body authentication (RFC 6749 Section 2.3.1)\n */\nfunction applyPostAuth(\n  clientId: string,\n  clientSecret: string | undefined,\n  params: URLSearchParams,\n): void {\n  params.set('client_id', clientId);\n  if (clientSecret) {","sourceCodeStart":821,"sourceCodeEnd":857,"githubUrl":"https://github.com/vercel/ai/blob/69428b1f8b037e4d118fb4853428d5c4e620493c/packages/mcp/src/tool/oauth.ts#L821-L857","documentation":"When the selected client authentication method is 'client_secret_basic' (HTTP Basic auth on the token endpoint), applyBasicAuth requires a client_secret to build the Base64 credentials. If clientSecret is falsy, it throws — a confidential client was registered for basic auth but no secret is available at token exchange/refresh time.","triggerScenarios":"exchangeAuthorization or refreshAuthorization with auth method 'client_secret_basic' while clientInformation has only a client_id (e.g. registration returned no secret, or stored client info lost the secret, or a public client was misconfigured as confidential).","commonSituations":"Restoring persisted client information that dropped client_secret (storage layer stripping fields), registering against an AS that issues public clients but the code still selects basic auth, or a provider whose clientInformation() returns a partial object.","solutions":["Ensure the clientSecret is present in the OAuthClientInformation used for the exchange (re-save with the secret from registration).","If the client is truly public, re-register with token_endpoint_auth_method 'none' (or 'client_secret_post' with a secret if confidential).","Check the persistence layer (provider.clientInformation()/saveClientInformation) isn't dropping the client_secret field.","Re-run dynamic registration (invalidateCredentials('all')) to obtain a fresh client_id/secret pair."],"exampleFix":"// before\nprovider.clientInformation = async () => ({ client_id: 'abc' }); // secret lost\n// after\nprovider.clientInformation = async () => ({ client_id: 'abc', client_secret: await loadSecret(), token_endpoint_auth_method: 'client_secret_basic' });","handlingStrategy":"validation","validationCode":"const info = await provider.clientInformation();\nif (info && (info.token_endpoint_auth_method ?? 'client_secret_basic') === 'client_secret_basic' && !info.client_secret) {\n  throw new Error('client_secret_basic selected but client_secret missing from stored client information');\n}","typeGuard":"function hasSecretForBasicAuth(info: { client_id: string; client_secret?: string; token_endpoint_auth_method?: string }): info is { client_id: string; client_secret: string } {\n  return (info.token_endpoint_auth_method ?? 'client_secret_basic') !== 'client_secret_basic' || typeof info.client_secret === 'string' && info.client_secret.length > 0;\n}","tryCatchPattern":"try {\n  await auth(provider, { serverUrl });\n} catch (error) {\n  if (String(error.message).includes('client_secret_basic authentication requires a client_secret')) {\n    await provider.invalidateCredentials?.('all');\n    await auth(provider, { serverUrl }); // re-register to obtain a secret\n  }\n}","preventionTips":["Persist the full registration response (including client_secret) in saveClientInformation.","Use a secret-capable storage backend; never strip client_secret from stored client info.","For public clients, explicitly set token_endpoint_auth_method to 'none'."],"tags":["oauth","client-secret","basic-auth","mcp"],"backgroundTag":"missing-client-secret","analyzedSha":"69428b1f8b037e4d118fb4853428d5c4e620493c","analyzedAt":"2026-08-30T12:32:21.016Z","schemaVersion":2},"datasetVersion":"2026-08-30T13:17:10.514Z"}