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
- 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.
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
- 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.
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
- Invalid state token format
- Invalid state token signature
- Google service account token request failed (${response.stat
- auth callback rejected (invalid state/token); 400 HTML failu
- Invalid state token format
AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30).
Data as JSON: /api/errors/7f5fa2fe62feae57.
Report an issue: GitHub.