{"record":{"id":"5ac845d7cff6197a","repo":"usememos/memos","slug":"the-signed-in-user-changed-before-the-oauth-callba","errorCode":null,"errorMessage":"The signed-in user changed before the OAuth callback completed. Please retry linking from account settings.","messagePattern":"The signed-in user changed before the OAuth callback completed\\. Please retry linking from account settings\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"warning","filePath":"web/src/pages/AuthCallback.tsx","lineNumber":90,"sourceCode":"      setState({\n        loading: false,\n        errorMessage: \"Failed to authorize. Invalid or expired state parameter. This may indicate a CSRF attack attempt.\",\n      });\n      return;\n    }\n\n    const { flowMode, identityProviderName, returnUrl, linkingUserName, codeVerifier } = validatedState;\n    const redirectUri = absolutifyLink(\"/auth/callback\");\n    handledRef.current = true;\n\n    (async () => {\n      try {\n        if (flowMode === \"link\") {\n          if (!currentUser?.name) {\n            throw new Error(\"Failed to link account. Please sign in to Memos again and retry.\");\n          }\n          if (linkingUserName && currentUser.name !== linkingUserName) {\n            throw new Error(\"The signed-in user changed before the OAuth callback completed. Please retry linking from account settings.\");\n          }\n          await userServiceClient.createLinkedIdentity({\n            parent: currentUser.name,\n            idpName: identityProviderName,\n            code,\n            redirectUri,\n            codeVerifier: codeVerifier || \"\",\n          });\n        } else {\n          const response = await authServiceClient.signIn({\n            credentials: {\n              case: \"ssoCredentials\",\n              value: {\n                idpName: identityProviderName,\n                code,\n                redirectUri,\n                codeVerifier: codeVerifier || \"\", // Pass PKCE code_verifier for token exchange\n              },","sourceCodeStart":72,"sourceCodeEnd":108,"githubUrl":"https://github.com/usememos/memos/blob/14d757ce1fb31c78590f374bc042f8dbedbc20d7/web/src/pages/AuthCallback.tsx#L72-L108","documentation":"Thrown in AuthCallback when the stored OAuth link state carries linkingUserName but the currently signed-in Memos user (currentUser.name) differs at callback time. It prevents attaching the IdP identity to the wrong account when the active session changed during the IdP round-trip (e.g., user signed out and back in as someone else in another tab).","triggerScenarios":"Start link as user A, then before the IdP redirect returns, sign out and sign in as user B (same browser) via BroadcastChannel-synced auth state; callback now sees currentUser.name !== linkingUserName and aborts createLinkedIdentity.","commonSituations":"Multi-account admins testing linking; sign-out in another tab mid-flow; dev environments where switching users between keycloak redirects is routine; stale tab completing an old link attempt after a user switch.","solutions":["Retry the linking flow from the account settings of the user you actually want to link, staying signed in as that user for the whole round-trip.","Close other Memos tabs before starting a link to avoid cross-tab user switches.","Sign out fully, sign in as the intended user, then link.","No code change needed — this is a deliberate safety abort; the identity has not been linked."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// at callback, before calling createLinkedIdentity\nif (linkingUserName && currentUser?.name && currentUser.name !== linkingUserName) {\n  showError(`Signed in as ${currentUser.name}, but the link was started by ${linkingUserName}. Retry from settings.`);\n  return;\n}","typeGuard":null,"tryCatchPattern":"catch (e) {\n  if (e instanceof Error && e.message.startsWith(\"The signed-in user changed\")) {\n    // safe no-op: identity was NOT linked; prompt retry\n    navigate(\"/settings\", { state: { relink: identityProviderName } });\n  }\n}","preventionTips":["Do not switch Memos accounts in other tabs while a link flow is in flight.","Sign out and back in as the intended user, then restart linking.","Treat this error as a guardrail: never bypass it by stripping the linkingUserName check."],"tags":["oauth","account-linking","frontend","security","race-condition"],"backgroundTag":null,"analyzedSha":"14d757ce1fb31c78590f374bc042f8dbedbc20d7","analyzedAt":"2026-08-15T09:27:36.538Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}