mastra-ai/mastra · error

Invalid authorization state

Error message

Invalid authorization state

What it means

`completeAnthropicLogin` validates the `state` portion of the pasted authorization input against the PKCE verifier returned by `startAnthropicLogin()`. It throws 'Invalid authorization state' when the state is absent or does not exactly equal the verifier persisted from the login start. This is the OAuth CSRF/replay defense: it proves the callback belongs to the login session that initiated it.

Source

Thrown at mastracode/sdk/src/auth/providers/anthropic.ts:67

    code_challenge_method: 'S256',
    state: verifier,
  });

  return { url: `${AUTHORIZE_URL}?${authParams.toString()}`, verifier };
}

/**
 * Complete an Anthropic login: parse the pasted authorization input
 * (full URL, `code#state`, or query string), validate its state, and exchange
 * it for tokens using the verifier from `startAnthropicLogin()`.
 */
export async function completeAnthropicLogin(input: string, verifier: string): Promise<OAuthCredentials> {
  const { code, state } = parseAuthorizationInput(input);
  if (!code) {
    throw new Error('Missing authorization code');
  }
  if (!state || state !== verifier) {
    throw new Error('Invalid authorization state');
  }

  const tokenResponse = await fetch(TOKEN_URL, {
    method: 'POST',
    // Bound the OAuth exchange so an unresponsive upstream cannot pin the
    // caller (and, in the shipyard server, the containing project lock)
    // indefinitely. See 2025-07-23 shipyard latency incident.
    signal: AbortSignal.timeout(15_000),
    headers: {
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      grant_type: 'authorization_code',
      client_id: CLIENT_ID,
      code,
      state,
      redirect_uri: REDIRECT_URI,
      code_verifier: verifier,

View on GitHub (pinned to 75dd419e61)

Solutions

  1. Restart the flow: call startAnthropicLogin() to get a fresh URL+verifier and have the user re-authorize — state mismatch is unrecoverable by design.
  2. Verify you persisted and passed the exact verifier from the same startAnthropicLogin() call that produced the authorization URL (check keying by session/user ID).
  3. Confirm the user pastes the entire `code#state` string; a missing or mangled `#state` yields an empty state and triggers this error.
  4. Ensure concurrent logins do not overwrite each other's stored verifier (scope storage per login session).

Example fix

// before
// verifier stored in a single module-level variable, shared across users
await completeAnthropicLogin(input, globalVerifier); // mismatch when two logins race
// after
const session = await sessionStore.get(loginSessionId);
if (!session?.verifier) throw new Error('No pending login session — restart the OAuth flow');
await completeAnthropicLogin(input, session.verifier);
Defensive patterns

Strategy: validation

Validate before calling

// ensure a verifier exists for this login session before completing
const session = await sessionStore.get(loginSessionId);
if (!session?.verifier || session.verifier.length < 43) {
  throw new Error('No pending login session — restart the OAuth flow');
}
await completeAnthropicLogin(input, session.verifier);

Type guard

function hasPendingLogin(s: unknown): s is { verifier: string } {
  return typeof s === 'object' && s !== null &&
    typeof (s as { verifier?: unknown }).verifier === 'string' &&
    (s as { verifier: string }).verifier.length > 0;
}

Try / catch

try {
  await completeAnthropicLogin(input, verifier);
} catch (e) {
  if (e instanceof Error && e.message === 'Invalid authorization state') {
    // state/verifier mismatch is unrecoverable: start a fresh login
    const { url, verifier: fresh } = await startAnthropicLogin();
    showAuthUrl(url);
    return completeAnthropicLogin(await promptForCode(), fresh);
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling completeAnthropicLogin with a verifier from a different (or restarted) login attempt than the one whose URL the user opened; the user pasting a code#state from an older authorization attempt; passing the code but truncating the `#state` suffix so state parses as empty; persisting the wrong verifier between two HTTP requests in a split start/complete flow.

Common situations: Server restarts between start and complete, losing the original verifier; user has two login tabs open and mixes their code/state pairs; race condition in storage where the verifier is overwritten by a concurrent login; string truncation of the pasted value at `#`.

Related errors


AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30). Data as JSON: /api/errors/c4d5f986a9ed83eb. Report an issue: GitHub.