{"record":{"id":"7f5fa2fe62feae57","repo":"mastra-ai/mastra","slug":"invalid-state-redirect-suffix","errorCode":null,"errorMessage":"Invalid state redirect suffix","messagePattern":"Invalid state redirect suffix","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"auth/google/src/auth-provider.ts","lineNumber":78,"sourceCode":"  return str.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n}\n\nfunction getServerRedirectStateSuffix(state: string): string {\n  const separatorIndex = state.indexOf('|');\n  return separatorIndex === -1 ? '' : state.slice(separatorIndex);\n}\n\nfunction getStateTokenFromCallbackState(state: string): string {\n  const separatorIndex = state.indexOf('|');\n  return separatorIndex === -1 ? state : state.slice(0, separatorIndex);\n}\n\nfunction verifyCallbackStateSuffix(callbackState: string, originalState: string): void {\n  const callbackSuffix = getServerRedirectStateSuffix(callbackState);\n  if (!callbackSuffix) return;\n\n  if (callbackSuffix !== getServerRedirectStateSuffix(originalState)) {\n    throw new Error('Invalid state redirect suffix');\n  }\n}\n\nfunction getExpirationMs(expiresAt: unknown): number | undefined {\n  if (expiresAt === undefined || expiresAt === null) {\n    return undefined;\n  }\n\n  if (expiresAt instanceof Date) {\n    return expiresAt.getTime();\n  }\n\n  if (typeof expiresAt === 'string' || typeof expiresAt === 'number') {\n    return new Date(expiresAt).getTime();\n  }\n\n  return Number.NaN;\n}","sourceCodeStart":60,"sourceCodeEnd":96,"githubUrl":"https://github.com/mastra-ai/mastra/blob/75dd419e613fe9c39f846ffc500716141b74fda6/auth/google/src/auth-provider.ts#L60-L96","documentation":"Google's SSO state tokens can carry a redirect-suffix portion. On callback, verifyCallbackStateSuffix extracts the suffix from the callback state and, if present, compares it to the suffix derived from the original state issued at login. A mismatch means the state was tampered with, belongs to a different redirect target, or was mixed up between concurrent flows, so the library throws to prevent an open-redirect/CSRF mix-up.","triggerScenarios":"attachSSOProvider's callback path calls verifyCallbackStateSuffix(callbackState, originalState) and `getServerRedirectStateSuffix(callbackState) !== getServerRedirectStateSuffix(originalState)` — i.e. the state returned by Google encodes a different redirect suffix than the one originally issued.","commonSituations":"Multiple SSO providers/redirect URIs behind one app where the callback route handles the wrong provider; state cookie/session overwritten by a parallel login in another tab; manually constructing or editing state strings; load balancer routing the callback to an instance with different configured redirect URIs.","solutions":["Restart the login flow so state is issued and consumed by the same configuration.","Verify each provider's configured redirect URI matches the callback route actually being hit (per-environment/per-provider routing).","Ensure the original state is persisted (cookie/session) per browser session and not shared or overwritten between concurrent logins.","Never build or alter state strings manually; always round-trip the value issued by getAuthorizationUri."],"exampleFix":"// before: shared state store across providers\nreq.session.oauthState = state;\n// after: namespace per provider/redirect\nreq.session[`oauthState:${providerId}`] = state;","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n  await provider.handleCallback(callbackUrl);\n} catch (e) {\n  if ((e as Error).message === 'Invalid state redirect suffix') {\n    logger.warn('State/redirect suffix mismatch — possible CSRF or cross-flow state reuse');\n    return restartLoginFlow();\n  }\n  throw e;\n}","preventionTips":["Namespace stored OAuth state per provider and per browser session to avoid cross-flow overwrite.","Ensure each provider's callback route matches its configured redirect URI.","Never construct or edit state strings manually; only round-trip issued values."],"tags":["auth","google","oauth","csrf","state-validation"],"backgroundTag":"oauth-state-mismatch","analyzedSha":"75dd419e613fe9c39f846ffc500716141b74fda6","analyzedAt":"2026-08-30T00:15:31.844Z","schemaVersion":2},"datasetVersion":"2026-08-30T03:17:51.788Z"}