vercel/ai · error
client_secret_basic authentication requires a client_secret
Error message
client_secret_basic authentication requires a client_secret
What it means
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.
Source
Thrown at packages/mcp/src/tool/oauth.ts:839
return;
case 'client_secret_post':
applyPostAuth(client_id, client_secret, params);
return;
case 'none':
applyPublicAuth(client_id, params);
return;
default:
throw new Error(`Unsupported client authentication method: ${method}`);
}
}
function applyBasicAuth(
clientId: string,
clientSecret: string | undefined,
headers: Headers,
): void {
if (!clientSecret) {
throw new Error(
'client_secret_basic authentication requires a client_secret',
);
}
const credentials = btoa(`${clientId}:${clientSecret}`);
headers.set('Authorization', `Basic ${credentials}`);
}
/**
* Applies POST body authentication (RFC 6749 Section 2.3.1)
*/
function applyPostAuth(
clientId: string,
clientSecret: string | undefined,
params: URLSearchParams,
): void {
params.set('client_id', clientId);
if (clientSecret) {View on GitHub (pinned to 69428b1f8b)
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.
Example fix
// before
provider.clientInformation = async () => ({ client_id: 'abc' }); // secret lost
// after
provider.clientInformation = async () => ({ client_id: 'abc', client_secret: await loadSecret(), token_endpoint_auth_method: 'client_secret_basic' }); Defensive patterns
Strategy: validation
Validate before calling
const info = await provider.clientInformation();
if (info && (info.token_endpoint_auth_method ?? 'client_secret_basic') === 'client_secret_basic' && !info.client_secret) {
throw new Error('client_secret_basic selected but client_secret missing from stored client information');
} Type guard
function hasSecretForBasicAuth(info: { client_id: string; client_secret?: string; token_endpoint_auth_method?: string }): info is { client_id: string; client_secret: string } {
return (info.token_endpoint_auth_method ?? 'client_secret_basic') !== 'client_secret_basic' || typeof info.client_secret === 'string' && info.client_secret.length > 0;
} Try / catch
try {
await auth(provider, { serverUrl });
} catch (error) {
if (String(error.message).includes('client_secret_basic authentication requires a client_secret')) {
await provider.invalidateCredentials?.('all');
await auth(provider, { serverUrl }); // re-register to obtain a secret
}
} Prevention
- 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'.
When it happens
Trigger: 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).
Common situations: 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.
Understand the failure class
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- OAuth protected resource metadata URL ${resourceMetadataUrl.
- Incompatible OIDC provider at ${endpointUrl}: does not suppo
- Incompatible auth server: does not support response type ${r
- Incompatible auth server: does not support code challenge me
- Unsupported client authentication method: ${method}
AI-assisted analysis of vercel/ai@69428b1f8b (2026-08-30).
Data as JSON: /api/errors/37e1139fce44db5f.
Report an issue: GitHub.