mastra-ai/mastra · error
Invalid or expired state parameter
Error message
Invalid or expired state parameter
What it means
Thrown by handleCallback when the stateId passed by the server does not exist in the in-memory stateStore. State is stored when the SSO flow starts and looked up (then deleted) on callback; a miss means the state was never created, was already consumed, or the process restarted (in-memory store lost).
Source
Thrown at auth/okta/src/auth-provider.ts:414
client_id: this.clientId,
response_type: 'code',
scope: this.scopes.join(' '),
redirect_uri: actualRedirectUri,
state,
});
return `${this.endpointBase}/v1/authorize?${params.toString()}`;
}
/**
* Handle the OAuth callback from Okta.
* Note: The server passes only the stateId (UUID part), not the full state.
*/
async handleCallback(code: string, stateId: string): Promise<SSOCallbackResult<OktaUser>> {
// Validate state parameter (server passes only the UUID part)
const stored = stateStore.get(stateId);
if (!stored) {
throw new Error('Invalid or expired state parameter');
}
stateStore.delete(stateId);
if (stored.expiresAt < Date.now()) {
throw new Error('State parameter has expired');
}
// Exchange code for tokens using client_secret (confidential client)
const tokenResponse = await fetch(`${this.endpointBase}/v1/token`, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
Authorization: `Basic ${btoa(`${this.clientId}:${this.clientSecret}`)}`,
},
body: new URLSearchParams({
grant_type: 'authorization_code',
code,
redirect_uri: stored.redirectUri,View on GitHub (pinned to 75dd419e61)
Solutions
- Have the user restart the SSO login flow from the beginning (fresh authorize redirect generates new state).
- Avoid replaying the callback — the state is single-use and deleted on first consumption.
- For multi-instance deployments, use a shared store (Redis/DB) for state or sticky sessions.
- Check that the authorize flow start and callback hit the same server process in development.
Defensive patterns
Strategy: try-catch
Validate before calling
if (!stateId || typeof stateId !== 'string') {
return res.status(400).json({ error: 'Missing state parameter' });
} Try / catch
try {
await provider.handleCallback(code, stateId);
} catch (e) {
if (e instanceof Error && e.message === 'Invalid or expired state parameter') {
// redirect user to restart SSO: /api/auth/sso/okta/authorize
} else throw e;
} Prevention
- Treat state as single-use; never retry the same callback request.
- Deploy a shared state store (Redis) or sticky sessions when running multiple instances.
- Disable response caching on the callback route so refreshes don't replay stale requests.
- Avoid hot-reload-driven state loss in development by restarting the flow after reloads.
When it happens
Trigger: Calling handleCallback(code, stateId) with a stateId that is not in stateStore: repeated callback (state deleted after first use), server restart between authorize redirect and callback, multiple server instances (state stored on another instance), or a forged/CSRF state value.
Common situations: User refreshes the callback URL and replays the request; horizontal scaling without a shared state store; dev server hot-reload clearing module memory; load balancer routing callback to a different pod.
Related errors
- State parameter has expired
- Okta client secret is required for SSO. Provide it in the op
- Okta redirect URI is required for SSO. Provide it in the opt
- Token exchange failed: ${error}
- Invalid state token format
AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30).
Data as JSON: /api/errors/ed9db91e70224702.
Report an issue: GitHub.