{"record":{"id":"f1e9833ea5dc030b","repo":"usememos/memos","slug":"failed-to-initialize-oauth-flow","errorCode":null,"errorMessage":"Failed to initialize OAuth flow","messagePattern":"Failed to initialize OAuth flow","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"web/src/utils/oauth.ts","lineNumber":94,"sourceCode":"    codeVerifier = undefined;\n    codeChallenge = undefined;\n  }\n\n  const stateData: OAuthState = {\n    state,\n    identityProviderName,\n    flowMode,\n    timestamp: Date.now(),\n    returnUrl,\n    linkingUserName,\n    codeVerifier, // Store for later retrieval in callback (undefined if PKCE not available)\n  };\n\n  try {\n    sessionStorage.setItem(STATE_STORAGE_KEY, JSON.stringify(stateData));\n  } catch (error) {\n    console.error(\"Failed to store OAuth state:\", error);\n    throw new Error(\"Failed to initialize OAuth flow\");\n  }\n\n  return { state, codeChallenge };\n}\n\n// Validate and retrieve OAuth state from storage (CSRF protection)\n// Returns identityProviderName, flowMode, returnUrl, linkingUserName, and codeVerifier for PKCE\nexport function validateOAuthState(\n  stateParam: string,\n): { identityProviderName: string; flowMode: OAuthFlowMode; returnUrl?: string; linkingUserName?: string; codeVerifier?: string } | null {\n  try {\n    const storedData = sessionStorage.getItem(STATE_STORAGE_KEY);\n    if (!storedData) {\n      console.error(\"No OAuth state found in storage\");\n      return null;\n    }\n\n    const stateData: OAuthState = JSON.parse(storedData);","sourceCodeStart":76,"sourceCodeEnd":112,"githubUrl":"https://github.com/usememos/memos/blob/14d757ce1fb31c78590f374bc042f8dbedbc20d7/web/src/utils/oauth.ts#L76-L112","documentation":"Thrown by initializeOAuthFlow when sessionStorage.setItem(STATE_STORAGE_KEY, ...) fails while persisting the CSRF state object before redirecting to the identity provider. sessionStorage can throw QuotaExceededError, SecurityError (cookies blocked in some iframe/Safari contexts), or be unavailable in private modes. Without stored state the OAuth callback cannot validate the redirect, so the flow aborts before leaving the page.","triggerScenarios":"Calling the OAuth init (signInWithIdp / link flow in web/src/utils/oauth.ts) when sessionStorage is full, blocked (Safari ITP / all-cookies-blocked), or accessed in a sandboxed iframe without allow-same-origin; also JSON.stringify of the state object itself cannot fail here, so the setItem call is the sole thrower.","commonSituations":"Browser set to block all cookies; Safari private mode; embedding Memos in an iframe; repeated large writes filling the 5MB origin quota; iOS WebView with storage partitioning.","solutions":["Check browser settings: allow cookies/site data for the Memos origin, and retry the sign-in.","If embedding Memos in an iframe, add allow-same-origin to the sandbox attributes or open in a top-level tab.","Clear the origin's sessionStorage (DevTools > Application > Session Storage) to free quota and retry.","As a code hardening step, prune stale STATE_STORAGE_KEY entries before writing and surface the underlying caught error (currently swallowed into console.error) for diagnosability."],"exampleFix":"// before\ntry {\n  sessionStorage.setItem(STATE_STORAGE_KEY, JSON.stringify(stateData));\n} catch (error) {\n  console.error(\"Failed to store OAuth state:\", error);\n  throw new Error(\"Failed to initialize OAuth flow\");\n}\n\n// after (surface cause + prune stale state)\ntry {\n  sessionStorage.removeItem(STATE_STORAGE_KEY);\n  sessionStorage.setItem(STATE_STORAGE_KEY, JSON.stringify(stateData));\n} catch (error) {\n  throw new Error(`Failed to initialize OAuth flow: ${error instanceof Error ? error.message : String(error)}`);\n}","handlingStrategy":"try-catch","validationCode":"function sessionStorageAvailable(): boolean {\n  try {\n    const probe = \"__oauth_probe__\";\n    sessionStorage.setItem(probe, \"1\");\n    sessionStorage.removeItem(probe);\n    return true;\n  } catch {\n    return false;\n  }\n}\n\n// before starting the flow:\nif (!sessionStorageAvailable()) {\n  showError(\"Enable site data/cookies to sign in with SSO.\");\n  return;\n}","typeGuard":"function isStorageError(e: unknown): e is DOMException {\n  return e instanceof DOMException && (e.name === \"QuotaExceededError\" || e.name === \"SecurityError\");\n}","tryCatchPattern":"try {\n  sessionStorage.setItem(STATE_STORAGE_KEY, JSON.stringify(stateData));\n} catch (error) {\n  if (error instanceof DOMException && error.name === \"QuotaExceededError\") {\n    sessionStorage.clear(); // or remove stale keys and retry once\n  }\n  throw new Error(`Failed to initialize OAuth flow: ${error instanceof Error ? error.message : String(error)}`);\n}","preventionTips":["Probe sessionStorage writability before showing the SSO sign-in button.","Prune the previous STATE_STORAGE_KEY entry before writing a new one.","Never embed Memos in a sandboxed iframe without allow-same-origin if SSO is used."],"tags":["oauth","sessionstorage","browser","frontend","csrf"],"backgroundTag":null,"analyzedSha":"14d757ce1fb31c78590f374bc042f8dbedbc20d7","analyzedAt":"2026-08-15T09:27:36.538Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}