{"record":{"id":"ed9db91e70224702","repo":"mastra-ai/mastra","slug":"invalid-or-expired-state-parameter","errorCode":null,"errorMessage":"Invalid or expired state parameter","messagePattern":"Invalid or expired state parameter","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"auth/okta/src/auth-provider.ts","lineNumber":414,"sourceCode":"      client_id: this.clientId,\n      response_type: 'code',\n      scope: this.scopes.join(' '),\n      redirect_uri: actualRedirectUri,\n      state,\n    });\n\n    return `${this.endpointBase}/v1/authorize?${params.toString()}`;\n  }\n\n  /**\n   * Handle the OAuth callback from Okta.\n   * Note: The server passes only the stateId (UUID part), not the full state.\n   */\n  async handleCallback(code: string, stateId: string): Promise<SSOCallbackResult<OktaUser>> {\n    // Validate state parameter (server passes only the UUID part)\n    const stored = stateStore.get(stateId);\n    if (!stored) {\n      throw new Error('Invalid or expired state parameter');\n    }\n    stateStore.delete(stateId);\n\n    if (stored.expiresAt < Date.now()) {\n      throw new Error('State parameter has expired');\n    }\n\n    // Exchange code for tokens using client_secret (confidential client)\n    const tokenResponse = await fetch(`${this.endpointBase}/v1/token`, {\n      method: 'POST',\n      headers: {\n        'Content-Type': 'application/x-www-form-urlencoded',\n        Authorization: `Basic ${btoa(`${this.clientId}:${this.clientSecret}`)}`,\n      },\n      body: new URLSearchParams({\n        grant_type: 'authorization_code',\n        code,\n        redirect_uri: stored.redirectUri,","sourceCodeStart":396,"sourceCodeEnd":432,"githubUrl":"https://github.com/mastra-ai/mastra/blob/75dd419e613fe9c39f846ffc500716141b74fda6/auth/okta/src/auth-provider.ts#L396-L432","documentation":"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).","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"if (!stateId || typeof stateId !== 'string') {\n  return res.status(400).json({ error: 'Missing state parameter' });\n}","typeGuard":null,"tryCatchPattern":"try {\n  await provider.handleCallback(code, stateId);\n} catch (e) {\n  if (e instanceof Error && e.message === 'Invalid or expired state parameter') {\n    // redirect user to restart SSO: /api/auth/sso/okta/authorize\n  } else throw e;\n}","preventionTips":["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."],"tags":["okta","sso","oauth-state","csrf"],"backgroundTag":"invalid-oauth-state","analyzedSha":"75dd419e613fe9c39f846ffc500716141b74fda6","analyzedAt":"2026-08-30T00:15:31.844Z","schemaVersion":2},"datasetVersion":"2026-08-30T03:17:51.788Z"}