vercel/ai · error
Existing OAuth client information is required when exchangin
Error message
Existing OAuth client information is required when exchanging an authorization code
What it means
In authInternal's resume/callback path, when an authorizationCode is present the client must exchange it at the token endpoint, which requires the client_id (and secret) obtained earlier via registration or supplied statically. If provider.clientInformation() returns nothing at this point, the state needed to complete the flow is missing and this error is thrown.
Source
Thrown at packages/mcp/src/tool/oauth.ts:1318
/** Load or register client credentials with the AS pin attached. */
let clientInformation = await Promise.resolve(provider.clientInformation());
if (clientInformation?.issuer != null) {
const storedAuthorizationServerInformation =
await getStoredAuthorizationServerInformation({
provider,
clientInformation,
});
if (storedAuthorizationServerInformation) {
assertAuthorizationServerInformationMatches({
storedAuthorizationServerInformation,
currentAuthorizationServerInformation,
});
}
}
if (!clientInformation) {
if (authorizationCode !== undefined) {
throw new Error(
'Existing OAuth client information is required when exchanging an authorization code',
);
}
if (!provider.saveClientInformation) {
throw new Error(
'OAuth client information must be saveable for dynamic registration',
);
}
const fullInformation = await registerClient(authorizationServerUrl, {
metadata,
clientMetadata: {
...clientMetadata,
scope: selectedScope,
},
fetchFn,
});View on GitHub (pinned to 69428b1f8b)
Solutions
- Implement saveClientInformation/clientInformation in your OAuthClientProvider to durably persist the registered client info (database, KV store, encrypted cookie) so it survives across callback requests.
- Alternatively supply static client credentials from clientInformation() so registration/persistence is never needed.
- Check that the storage backend used between the two auth() calls isn't being cleared (serverless cold starts, in-memory maps).
- Restart the flow from scratch (drop the stale authorizationCode and call auth() without it) if client state is unrecoverable.
Example fix
// before: client info only in memory, lost before callback
const clients = new Map();
// after: persist across callback
async saveClientInformation(clientInformation) {
await kv.set('oauth:client', JSON.stringify(clientInformation));
}
async clientInformation() {
const raw = await kv.get('oauth:client');
return raw ? JSON.parse(raw) : undefined;
} Defensive patterns
Strategy: try-catch
Validate before calling
const info = await provider.clientInformation();
if (!info && isOAuthCallback(request)) {
throw new Error('Client information missing at callback; ensure saveClientInformation persisted it during the initial auth() call');
} Try / catch
try {
await auth(provider, { serverUrl, authorizationCode });
} catch (error) {
if (String(error.message).includes('Existing OAuth client information is required')) {
// state lost between redirect and callback: restart the whole flow
await provider.invalidateCredentials?.('all');
return restartAuthorization(provider, serverUrl);
}
throw error;
} Prevention
- Persist client information in a shared store (DB/KV), never in-process memory, especially on serverless.
- Always implement both saveClientInformation and clientInformation symmetrically.
- Or bypass dynamic registration entirely with static client credentials.
- Test the full authorize→callback round-trip across process restarts before shipping.
When it happens
Trigger: Calling auth() with authorizationCode set (the OAuth redirect callback) while provider.clientInformation() returns undefined — typically because client information from the initial authorization step was never persisted or cannot be loaded.
Common situations: Multi-instance/serverless deployments where the first auth() call registered a client in-memory on one instance but the callback lands on another; a provider implementation lacking saveClientInformation persistence; storage reset between authorization and callback.
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/b46caf82f9e2a48f.
Report an issue: GitHub.