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

  1. Implement saveClientInformation/clientInformation in your OAuthClientProvider to durably persist the registered client info (database, KV store, encrypted cookie) so it survives across callback requests.
  2. Alternatively supply static client credentials from clientInformation() so registration/persistence is never needed.
  3. Check that the storage backend used between the two auth() calls isn't being cleared (serverless cold starts, in-memory maps).
  4. 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

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


AI-assisted analysis of vercel/ai@69428b1f8b (2026-08-30). Data as JSON: /api/errors/b46caf82f9e2a48f. Report an issue: GitHub.