usememos/memos · error · Error

Failed to initialize OAuth flow

Error message

Failed to initialize OAuth flow

What it means

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.

Solutions

  1. Check browser settings: allow cookies/site data for the Memos origin, and retry the sign-in.
  2. If embedding Memos in an iframe, add allow-same-origin to the sandbox attributes or open in a top-level tab.
  3. Clear the origin's sessionStorage (DevTools > Application > Session Storage) to free quota and retry.
  4. 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.

Example fix

// before
try {
  sessionStorage.setItem(STATE_STORAGE_KEY, JSON.stringify(stateData));
} catch (error) {
  console.error("Failed to store OAuth state:", error);
  throw new Error("Failed to initialize OAuth flow");
}

// after (surface cause + prune stale state)
try {
  sessionStorage.removeItem(STATE_STORAGE_KEY);
  sessionStorage.setItem(STATE_STORAGE_KEY, JSON.stringify(stateData));
} catch (error) {
  throw new Error(`Failed to initialize OAuth flow: ${error instanceof Error ? error.message : String(error)}`);
}
Defensive patterns

Strategy: try-catch

Validate before calling

function sessionStorageAvailable(): boolean {
  try {
    const probe = "__oauth_probe__";
    sessionStorage.setItem(probe, "1");
    sessionStorage.removeItem(probe);
    return true;
  } catch {
    return false;
  }
}

// before starting the flow:
if (!sessionStorageAvailable()) {
  showError("Enable site data/cookies to sign in with SSO.");
  return;
}

Type guard

function isStorageError(e: unknown): e is DOMException {
  return e instanceof DOMException && (e.name === "QuotaExceededError" || e.name === "SecurityError");
}

Try / catch

try {
  sessionStorage.setItem(STATE_STORAGE_KEY, JSON.stringify(stateData));
} catch (error) {
  if (error instanceof DOMException && error.name === "QuotaExceededError") {
    sessionStorage.clear(); // or remove stale keys and retry once
  }
  throw new Error(`Failed to initialize OAuth flow: ${error instanceof Error ? error.message : String(error)}`);
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of usememos/memos@14d757ce1f (2026-08-15). Data as JSON: /api/errors/f1e9833ea5dc030b. Report an issue: GitHub.

Appendix: source

Thrown at web/src/utils/oauth.ts:94

    codeVerifier = undefined;
    codeChallenge = undefined;
  }

  const stateData: OAuthState = {
    state,
    identityProviderName,
    flowMode,
    timestamp: Date.now(),
    returnUrl,
    linkingUserName,
    codeVerifier, // Store for later retrieval in callback (undefined if PKCE not available)
  };

  try {
    sessionStorage.setItem(STATE_STORAGE_KEY, JSON.stringify(stateData));
  } catch (error) {
    console.error("Failed to store OAuth state:", error);
    throw new Error("Failed to initialize OAuth flow");
  }

  return { state, codeChallenge };
}

// Validate and retrieve OAuth state from storage (CSRF protection)
// Returns identityProviderName, flowMode, returnUrl, linkingUserName, and codeVerifier for PKCE
export function validateOAuthState(
  stateParam: string,
): { identityProviderName: string; flowMode: OAuthFlowMode; returnUrl?: string; linkingUserName?: string; codeVerifier?: string } | null {
  try {
    const storedData = sessionStorage.getItem(STATE_STORAGE_KEY);
    if (!storedData) {
      console.error("No OAuth state found in storage");
      return null;
    }

    const stateData: OAuthState = JSON.parse(storedData);

View on GitHub (pinned to 14d757ce1f)