decolua/9router · error
Invalid state parameter
Error message
Invalid state parameter
What it means
Thrown by OAuthService.authenticate() after the callback arrives: the `state` query param returned by the provider does not equal the random `state` generated locally by generatePKCE() at the start of the flow. This is a CSRF/session-mixup guard — the library refuses to proceed because the callback may not belong to this authentication attempt.
Source
Thrown at src/lib/oauth/services/oauth.js:146
// Start local server and get redirect URI
const { redirectUri, waitForCallback } = await this.startAuthFlow(null, providerName);
// Build authorization URL
const authUrl = buildAuthUrlFn(redirectUri, state, codeChallenge);
console.log(`\nOpening browser for ${providerName} authentication...`);
console.log(`If browser doesn't open, visit:\n${authUrl}\n`);
// Open browser
await open(authUrl);
// Wait for callback
const callbackParams = await waitForCallback();
// Validate state
if (callbackParams.state !== state) {
throw new Error("Invalid state parameter");
}
return {
code: callbackParams.code,
state: callbackParams.state,
codeVerifier,
redirectUri,
};
}
}
View on GitHub (pinned to 90b52e06ff)
Solutions
- Close all stale tabs from previous auth attempts and restart the flow in a single browser tab.
- Run only one authentication flow at a time; concurrent flows can bind the same localhost port and cross-deliver callbacks.
- Re-run the flow and complete it promptly (within the 5-minute timeout) without manually editing the URL.
- If a proxy strips query params, bypass it for localhost callbacks or complete auth outside the proxied network.
Example fix
// before
const callbackParams = await waitForCallback();
if (callbackParams.state !== state) {
throw new Error("Invalid state parameter");
}
// after (fail fast with both values for diagnosis)
if (callbackParams.state !== state) {
throw new Error(`Invalid state parameter: expected ${state}, got ${callbackParams.state}`);
} Defensive patterns
Strategy: try-catch
Try / catch
try {
const result = await service.authenticate(provider, buildUrlFn);
} catch (err) {
if (err.message === "Invalid state parameter") {
// stale tab or concurrent flow — clean up and retry once, serially
} else throw err;
} Prevention
- Run only one OAuth flow at a time; concurrent flows can share the localhost callback port.
- Close leftover tabs from previous failed auth attempts before retrying.
- Complete the flow within the timeout window without manually editing URLs.
- Don't bookmark or reuse authorize URLs — each flow needs a fresh state.
When it happens
Trigger: authenticate() -> waitForCallback() resolves with callbackParams whose `state` differs from the locally generated one — e.g. a stale tab from a previous run completing the callback on the reused localhost port, two CLI auth flows running concurrently on the same port, the provider dropping/rewriting the state param, or the callback URL being manually edited.
Common situations: An old browser tab from a previous failed auth attempt finally redirects and hits the new local server first; running two `connect` flows in parallel terminals where one server steals the other's callback; proxies or SSO intermediaries stripping query parameters; cookie/partitioned browser sessions mixing flows.
Related errors
- `Token exchange failed: ${error}`
- `Token exchange failed: ${error}`
- Windsurf callback state mismatch
- xAI token exchange failed: ${error}
- Token exchange failed: ${error}
AI-assisted analysis of decolua/9router@90b52e06ff (2026-08-30).
Data as JSON: /api/errors/afc976f268f332fd.
Report an issue: GitHub.