decolua/9router · error

Invalid state parameter

Error message

Invalid state parameter

What it means

connect() generates a random `state` value, embeds it in the authorize URL, and requires the callback to echo the identical state. A mismatch means the callback did not originate from the authorization request this run started — the library refuses it as a CSRF protection. The flow is aborted before the token exchange.

Source

Thrown at src/lib/oauth/services/xai.js:220

      spinner.start("Waiting for xAI authorization...");
      await new Promise((resolve, reject) => {
        const timeout = setTimeout(() => reject(new Error("Authentication timeout (5 minutes)")), 300000);
        const iv = setInterval(() => {
          if (callbackParams) {
            clearInterval(iv);
            clearTimeout(timeout);
            resolve();
          }
        }, 100);
      });
      close();

      if (callbackParams.error) {
        throw new Error(callbackParams.error_description || callbackParams.error);
      }
      if (!callbackParams.code) throw new Error("No authorization code received");
      if (callbackParams.state !== state) throw new Error("Invalid state parameter");

      spinner.start("Exchanging code for tokens...");
      const tokens = await this.exchangeXaiCode({
        tokenUrl,
        code: callbackParams.code,
        redirectUri,
        codeVerifier,
      });

      const email = decodeIdTokenEmail(tokens.id_token);
      spinner.succeed("xAI connected successfully!");
      return { tokens, email };
    } catch (error) {
      spinner.fail(`Failed: ${error.message}`);
      throw error;
    }
  }
}

View on GitHub (pinned to 90b52e06ff)

Solutions

  1. Re-run connect() and use the current browser tab/URL it prints — do not reuse an auth URL from a previous attempt
  2. Close stale auth tabs from earlier attempts so an old callback cannot arrive first
  3. Avoid running two connect() flows concurrently for the same provider
  4. Do not modify the authUrl; let open() launch it exactly as generated
  5. If a corporate proxy rewrites URLs, bypass it for 127.0.0.1 loopback redirects
Defensive patterns

Strategy: try-catch

Validate before calling

// Verify state on your own callback handler before relying on connect().
const params = new URL(callbackUrl).searchParams;
if (params.get('state') !== expectedState) {
  console.log('State mismatch — stale or forged callback; restart the flow.');
}

Try / catch

try {
  await xai.connect();
} catch (err) {
  if (err.message === 'Invalid state parameter') {
    // stale/concurrent flow — rerun connect() with a fresh tab
  } else {
    throw err;
  }
}

Prevention

When it happens

Trigger: During xaiOAuthService.connect(), the callback's `state` query parameter differs from the `state` generated for this session (callbackParams.state !== state).

Common situations: Two connect() runs overlap and a stale browser tab from an earlier attempt delivers the old callback; the state got truncated/URL-encoded differently in transit (manual URL editing, redirect through a tool that rewrites the query); sticky browser cache replayed an old redirect; the user copy-pasted a previous authUrl instead of the freshly printed one.

Related errors


AI-assisted analysis of decolua/9router@90b52e06ff (2026-08-30). Data as JSON: /api/errors/d386e384313eeb41. Report an issue: GitHub.