{"record":{"id":"b33a13c2b0630859","repo":"moeru-ai/airi","slug":"oidc-flow-status-has-expired-or-is-no-longer-valid","errorCode":null,"errorMessage":"OIDC flow status has expired or is no longer valid.","messagePattern":"OIDC flow status has expired or is no longer valid\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/stage-ui/src/libs/auth.ts","lineNumber":103,"sourceCode":"    callbackURL: authorizationUrl,\n  })\n}\n\n/**\n * Completes an OIDC sign-in from a platform callback URL.\n *\n * The function returns false when the URL is not an OIDC callback.\n */\nexport async function completeOIDCSignIn(callbackUrl: string): Promise<boolean> {\n  const url = new URL(callbackUrl)\n  const code = url.searchParams.get('code')\n  const state = url.searchParams.get('state')\n  if (!code || !state)\n    return false\n\n  const persisted = consumeFlowState()\n  if (!persisted)\n    throw new Error('OIDC flow status has expired or is no longer valid.')\n\n  const tokens = await exchangeCodeForTokens(code, persisted.flowState, persisted.params, state)\n  await applyOIDCTokens(tokens, persisted.params.clientId)\n  return true\n}\n\n/**\n * Initiate OIDC Authorization Code + PKCE sign-in flow.\n * Builds the authorization URL, persists PKCE state, and navigates.\n */\nexport async function signInOIDC(params: OIDCFlowParams) {\n  const handler = authorizationHandler\n  if (!handler)\n    throw new Error('No authorization handler is registered for this app runtime.')\n\n  const { provider, ...oidcParams } = params\n  const { url, flowState } = await buildAuthorizationURL(oidcParams)\n  persistFlowState(flowState, params)","sourceCodeStart":85,"sourceCodeEnd":121,"githubUrl":"https://github.com/moeru-ai/airi/blob/0616eabd5ba89c7bcef0eb1da84694b9c285c573/packages/stage-ui/src/libs/auth.ts#L85-L121","documentation":"completeOIDCSignIn throws this when the callback URL contains a code and state but consumeFlowState() returns nothing, meaning no PKCE/flow state was persisted (or it was consumed or expired) for this sign-in attempt. The OIDC Authorization Code + PKCE flow requires the locally stored state (code verifier, params) to match the callback; without it the code cannot be exchanged safely.","triggerScenarios":"signInOIDC was never called (or ran in another window/session) before the redirect callback hit completeOIDCSignIn; the flow state was already consumed by a duplicate callback handling; storage was cleared or is unavailable (localStorage disabled, private mode, different origin/window); the user bookmarked or reloaded the callback URL after the state was consumed.","commonSituations":"Deep-linking the callback URL in an Electron app where the state was persisted in a different window; reloading or re-opening the OIDC redirect URI; browser storage partitioning or third-party storage blocking; two components both handling the OAuth redirect and the first consuming the state.","solutions":["Restart the flow: call signInOIDC again to build a fresh authorization URL and persisted state, then complete sign-in from that attempt.","Ensure completeOIDCSignIn runs in the same window/session that called signInOIDC (check storage access in iframes/private mode/Electron partitions).","Make sure the redirect is handled exactly once — remove duplicate route handlers or listeners that consume the flow state first.","Check that storage is enabled and the app origin is stable between initiation and callback."],"exampleFix":"// before\nconst ok = await completeOIDCSignIn(window.location)\nif (!ok) return\n\n// after\ntry {\n  const ok = await completeOIDCSignIn(window.location)\n  if (!ok) return\n} catch (err) {\n  // stale callback (reload/bookmark): restart the flow\n  await signInOIDC(params)\n}","handlingStrategy":"fallback","validationCode":"const hasFlowState = readFlowState() != null // or expose a probe from the auth lib\nif (!hasFlowState) {\n  // stale/duplicate callback: restart instead of completing\n  await signInOIDC(params)\n} else {\n  await completeOIDCSignIn(url)\n}","typeGuard":"null","tryCatchPattern":"try {\n  await completeOIDCSignIn(window.location)\n} catch (err) {\n  if (err instanceof Error && err.message.includes('expired or is no longer valid')) {\n    await signInOIDC(params) // restart the flow once; surface UI if it repeats\n  } else throw err\n}","preventionTips":["Handle the OIDC redirect exactly once — remove duplicate listeners/route guards that consume flow state.","Avoid reloading or bookmarking callback URLs; treat callbacks as single-use.","Run initiation and completion in the same window/session/storage partition.","Show a 'session expired, sign in again' UI instead of an error page on stale callbacks."],"tags":["oidc","auth","pkce","state-expired","storage"],"backgroundTag":"oauth-state-mismatch","analyzedSha":"0616eabd5ba89c7bcef0eb1da84694b9c285c573","analyzedAt":"2026-08-28T13:11:01.995Z","contentChangedAt":"2026-08-28T13:11:01.995Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}