{"record":{"id":"c4d5f986a9ed83eb","repo":"mastra-ai/mastra","slug":"invalid-authorization-state","errorCode":null,"errorMessage":"Invalid authorization state","messagePattern":"Invalid authorization state","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"mastracode/sdk/src/auth/providers/anthropic.ts","lineNumber":67,"sourceCode":"    code_challenge_method: 'S256',\n    state: verifier,\n  });\n\n  return { url: `${AUTHORIZE_URL}?${authParams.toString()}`, verifier };\n}\n\n/**\n * Complete an Anthropic login: parse the pasted authorization input\n * (full URL, `code#state`, or query string), validate its state, and exchange\n * it for tokens using the verifier from `startAnthropicLogin()`.\n */\nexport async function completeAnthropicLogin(input: string, verifier: string): Promise<OAuthCredentials> {\n  const { code, state } = parseAuthorizationInput(input);\n  if (!code) {\n    throw new Error('Missing authorization code');\n  }\n  if (!state || state !== verifier) {\n    throw new Error('Invalid authorization state');\n  }\n\n  const tokenResponse = await fetch(TOKEN_URL, {\n    method: 'POST',\n    // Bound the OAuth exchange so an unresponsive upstream cannot pin the\n    // caller (and, in the shipyard server, the containing project lock)\n    // indefinitely. See 2025-07-23 shipyard latency incident.\n    signal: AbortSignal.timeout(15_000),\n    headers: {\n      'Content-Type': 'application/json',\n    },\n    body: JSON.stringify({\n      grant_type: 'authorization_code',\n      client_id: CLIENT_ID,\n      code,\n      state,\n      redirect_uri: REDIRECT_URI,\n      code_verifier: verifier,","sourceCodeStart":49,"sourceCodeEnd":85,"githubUrl":"https://github.com/mastra-ai/mastra/blob/75dd419e613fe9c39f846ffc500716141b74fda6/mastracode/sdk/src/auth/providers/anthropic.ts#L49-L85","documentation":"`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.","triggerScenarios":"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.","commonSituations":"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 `#`.","solutions":["Restart the flow: call startAnthropicLogin() to get a fresh URL+verifier and have the user re-authorize — state mismatch is unrecoverable by design.","Verify you persisted and passed the exact verifier from the same startAnthropicLogin() call that produced the authorization URL (check keying by session/user ID).","Confirm the user pastes the entire `code#state` string; a missing or mangled `#state` yields an empty state and triggers this error.","Ensure concurrent logins do not overwrite each other's stored verifier (scope storage per login session)."],"exampleFix":"// before\n// verifier stored in a single module-level variable, shared across users\nawait completeAnthropicLogin(input, globalVerifier); // mismatch when two logins race\n// after\nconst session = await sessionStore.get(loginSessionId);\nif (!session?.verifier) throw new Error('No pending login session — restart the OAuth flow');\nawait completeAnthropicLogin(input, session.verifier);","handlingStrategy":"validation","validationCode":"// ensure a verifier exists for this login session before completing\nconst session = await sessionStore.get(loginSessionId);\nif (!session?.verifier || session.verifier.length < 43) {\n  throw new Error('No pending login session — restart the OAuth flow');\n}\nawait completeAnthropicLogin(input, session.verifier);","typeGuard":"function hasPendingLogin(s: unknown): s is { verifier: string } {\n  return typeof s === 'object' && s !== null &&\n    typeof (s as { verifier?: unknown }).verifier === 'string' &&\n    (s as { verifier: string }).verifier.length > 0;\n}","tryCatchPattern":"try {\n  await completeAnthropicLogin(input, verifier);\n} catch (e) {\n  if (e instanceof Error && e.message === 'Invalid authorization state') {\n    // state/verifier mismatch is unrecoverable: start a fresh login\n    const { url, verifier: fresh } = await startAnthropicLogin();\n    showAuthUrl(url);\n    return completeAnthropicLogin(await promptForCode(), fresh);\n  }\n  throw e;\n}","preventionTips":["Persist the verifier keyed by session/user ID, not in a module-level or global variable.","In split start/complete flows, survive restarts by storing the verifier durably (DB/redis), not in memory.","Instruct users to paste the complete `code#state` string — truncation drops the state.","Discard stale in-flight logins when starting a new one to avoid mixed code/state pairs."],"tags":["oauth","anthropic","pkce","csrf","state-mismatch"],"backgroundTag":"oauth-state-mismatch","analyzedSha":"75dd419e613fe9c39f846ffc500716141b74fda6","analyzedAt":"2026-08-30T00:15:31.844Z","schemaVersion":2},"datasetVersion":"2026-08-30T03:17:51.788Z"}