mastra-ai/mastra · error

Invalid state redirect suffix

Error message

Invalid state redirect suffix

What it means

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.

Source

Thrown at auth/google/src/auth-provider.ts:78

  return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}

function getServerRedirectStateSuffix(state: string): string {
  const separatorIndex = state.indexOf('|');
  return separatorIndex === -1 ? '' : state.slice(separatorIndex);
}

function getStateTokenFromCallbackState(state: string): string {
  const separatorIndex = state.indexOf('|');
  return separatorIndex === -1 ? state : state.slice(0, separatorIndex);
}

function verifyCallbackStateSuffix(callbackState: string, originalState: string): void {
  const callbackSuffix = getServerRedirectStateSuffix(callbackState);
  if (!callbackSuffix) return;

  if (callbackSuffix !== getServerRedirectStateSuffix(originalState)) {
    throw new Error('Invalid state redirect suffix');
  }
}

function getExpirationMs(expiresAt: unknown): number | undefined {
  if (expiresAt === undefined || expiresAt === null) {
    return undefined;
  }

  if (expiresAt instanceof Date) {
    return expiresAt.getTime();
  }

  if (typeof expiresAt === 'string' || typeof expiresAt === 'number') {
    return new Date(expiresAt).getTime();
  }

  return Number.NaN;
}

View on GitHub (pinned to 75dd419e61)

Solutions

  1. Restart the login flow so state is issued and consumed by the same configuration.
  2. Verify each provider's configured redirect URI matches the callback route actually being hit (per-environment/per-provider routing).
  3. Ensure the original state is persisted (cookie/session) per browser session and not shared or overwritten between concurrent logins.
  4. Never build or alter state strings manually; always round-trip the value issued by getAuthorizationUri.

Example fix

// before: shared state store across providers
req.session.oauthState = state;
// after: namespace per provider/redirect
req.session[`oauthState:${providerId}`] = state;
Defensive patterns

Strategy: try-catch

Try / catch

try {
  await provider.handleCallback(callbackUrl);
} catch (e) {
  if ((e as Error).message === 'Invalid state redirect suffix') {
    logger.warn('State/redirect suffix mismatch — possible CSRF or cross-flow state reuse');
    return restartLoginFlow();
  }
  throw e;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


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