{"record":{"id":"d386e384313eeb41","repo":"decolua/9router","slug":"invalid-state-parameter-d386e3","errorCode":null,"errorMessage":"Invalid state parameter","messagePattern":"Invalid state parameter","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/lib/oauth/services/xai.js","lineNumber":220,"sourceCode":"\n      spinner.start(\"Waiting for xAI authorization...\");\n      await new Promise((resolve, reject) => {\n        const timeout = setTimeout(() => reject(new Error(\"Authentication timeout (5 minutes)\")), 300000);\n        const iv = setInterval(() => {\n          if (callbackParams) {\n            clearInterval(iv);\n            clearTimeout(timeout);\n            resolve();\n          }\n        }, 100);\n      });\n      close();\n\n      if (callbackParams.error) {\n        throw new Error(callbackParams.error_description || callbackParams.error);\n      }\n      if (!callbackParams.code) throw new Error(\"No authorization code received\");\n      if (callbackParams.state !== state) throw new Error(\"Invalid state parameter\");\n\n      spinner.start(\"Exchanging code for tokens...\");\n      const tokens = await this.exchangeXaiCode({\n        tokenUrl,\n        code: callbackParams.code,\n        redirectUri,\n        codeVerifier,\n      });\n\n      const email = decodeIdTokenEmail(tokens.id_token);\n      spinner.succeed(\"xAI connected successfully!\");\n      return { tokens, email };\n    } catch (error) {\n      spinner.fail(`Failed: ${error.message}`);\n      throw error;\n    }\n  }\n}","sourceCodeStart":202,"sourceCodeEnd":238,"githubUrl":"https://github.com/decolua/9router/blob/90b52e06ffd666b7929554211474d01588f6b1f8/src/lib/oauth/services/xai.js#L202-L238","documentation":"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.","triggerScenarios":"During xaiOAuthService.connect(), the callback's `state` query parameter differs from the `state` generated for this session (callbackParams.state !== state).","commonSituations":"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.","solutions":["Re-run connect() and use the current browser tab/URL it prints — do not reuse an auth URL from a previous attempt","Close stale auth tabs from earlier attempts so an old callback cannot arrive first","Avoid running two connect() flows concurrently for the same provider","Do not modify the authUrl; let open() launch it exactly as generated","If a corporate proxy rewrites URLs, bypass it for 127.0.0.1 loopback redirects"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"// Verify state on your own callback handler before relying on connect().\nconst params = new URL(callbackUrl).searchParams;\nif (params.get('state') !== expectedState) {\n  console.log('State mismatch — stale or forged callback; restart the flow.');\n}","typeGuard":null,"tryCatchPattern":"try {\n  await xai.connect();\n} catch (err) {\n  if (err.message === 'Invalid state parameter') {\n    // stale/concurrent flow — rerun connect() with a fresh tab\n  } else {\n    throw err;\n  }\n}","preventionTips":["Use the auth URL printed by the current run, never an old one","Close stale authorization tabs from previous attempts","Run only one OAuth flow at a time per provider","Don't hand-edit the authUrl or its query string","Bypass proxies that rewrite URLs for 127.0.0.1 redirects"],"tags":["oauth","xai","csrf","state-mismatch","callback"],"backgroundTag":"oauth-state-mismatch","analyzedSha":"90b52e06ffd666b7929554211474d01588f6b1f8","analyzedAt":"2026-08-30T21:05:45.952Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}