{"record":{"id":"55e692706f4d2bbb","repo":"makeplane/plane","slug":"auth-user-mismatch","errorCode":"AUTH_USER_MISMATCH","errorMessage":"Authentication unsuccessful: User ID mismatch","messagePattern":"Authentication unsuccessful: User ID mismatch","errorType":"exception","errorClass":"AppError","httpStatus":null,"severity":"critical","filePath":"apps/live/src/lib/auth.ts","lineNumber":81,"sourceCode":"  context.cookie = cookie ?? requestParameters.get(\"cookie\") ?? \"\";\n  context.documentType = requestParameters.get(\"documentType\")?.toString() as TDocumentTypes;\n  context.projectId = requestParameters.get(\"projectId\");\n  context.userId = userId;\n  context.workspaceSlug = requestParameters.get(\"workspaceSlug\");\n\n  return await handleAuthentication({\n    cookie: context.cookie,\n    userId: context.userId,\n  });\n};\n\nexport const handleAuthentication = async ({ cookie, userId }: { cookie: string; userId: string }) => {\n  // fetch current user info\n  try {\n    const userService = new UserService();\n    const user = await userService.currentUser(cookie);\n    if (user.id !== userId) {\n      throw new AppError(\"Authentication unsuccessful: User ID mismatch\", { code: \"AUTH_USER_MISMATCH\" });\n    }\n\n    return {\n      user: {\n        id: user.id,\n        name: user.display_name,\n      },\n    };\n  } catch (error) {\n    const appError = new AppError(error, {\n      context: { operation: \"handleAuthentication\" },\n    });\n    logger.error(\"Authentication failed\", appError);\n    throw new AppError(\"Authentication unsuccessful\", { code: appError.code });\n  }\n};\n","sourceCodeStart":63,"sourceCodeEnd":98,"githubUrl":"https://github.com/makeplane/plane/blob/1c8a60f858d8472aa56e29994ec1c7926da2c6ce/apps/live/src/lib/auth.ts#L63-L98","documentation":"Thrown by handleAuthentication in the live (HocusPocus / Yjs websocket) auth handler when the user ID resolved from the session cookie via UserService.currentUser() does not match the userId claim carried in the connection token. It is a deliberate identity-integrity check: the websocket connection must not be bound to a user other than the one the API says the cookie belongs to. The AppError carries code AUTH_USER_MISMATCH and is the strongest auth signal short of missing credentials.","triggerScenarios":"A websocket (live document) connection where the token's `id` field (parsed from the HocusPocus token JSON at auth.ts:41-43) disagrees with `user.id` returned by `userService.currentUser(cookie)`. Concretely: a stale token stored in the browser from a previous login, a token forged/mistuned by a custom client, the cookie belonging to a different (e.g. service) account than the token, or a token replayed in a different session after the user was re-created with a new ID.","commonSituations":"User logged in as account A, account was deleted and recreated, browser still holds the old token; multi-tab scenarios where one tab refreshed auth and another did not; SSO/impersonation flows that swap the cookie without reissuing the token; local/dev environments where a developer hand-crafted a token JSON with the wrong id.","solutions":["Force a full re-authentication on the client: clear the cookie/token (sign out) and sign in again so a fresh token+cookie pair is issued together.","Verify the token payload the client sends to the websocket matches the authenticated session: inspect the JSON passed as the HocusPocus `token` parameter and confirm its `id` equals the API's current user id.","If using a custom client or proxy, ensure it forwards the same cookie used for the REST `/users/me` call as the token's `id` claim, instead of a hard-coded or cached id.","Check that UserService.currentUser is hitting the same backend/cookie domain as the REST app; a cross-domain cookie mismatch can resolve to a different user."],"exampleFix":"// before: client stores token once and never refreshes it\nconst token = JSON.stringify({ id: cachedUserId, cookie: cachedCookie });\n// after: always derive the token from the live session right before connecting\nconst me = await api.get('/users/me/');\nconst token = JSON.stringify({ id: me.id, cookie: document.cookie });","handlingStrategy":"validation","validationCode":"// Client-side, before opening the websocket: confirm token id matches the live session\nconst me = await fetch('/api/users/me/', { credentials: 'include' }).then(r => r.ok ? r.json() : null);\nconst tokenPayload = JSON.parse(storedToken);\nif (!me || me.id !== tokenPayload.id) {\n  // refresh the token pair before connecting\n  await refreshSession();\n}","typeGuard":"const isUserDetailsToken = (t: unknown): t is { id: string; cookie: string } =>\n  typeof t === 'object' && t !== null &&\n  typeof (t as any).id === 'string' &&\n  typeof (t as any).cookie === 'string';","tryCatchPattern":"try { await handleAuthentication({ cookie, userId }); }\ncatch (e) {\n  if (e instanceof AppError && e.code === 'AUTH_USER_MISMATCH') {\n    // force re-login: token does not match session\n    await signOutAndRedirect();\n  } else throw e;\n}","preventionTips":["Always derive the websocket token from the live session at connect time, never from a long-lived cache.","On any auth failure, clear the stored token and cookie before re-authenticating.","Rotate tokens on sign-in/impersonation flows and invalidate the old pair."],"tags":["auth","websocket","live","session","token-mismatch"],"backgroundTag":null,"analyzedSha":"1c8a60f858d8472aa56e29994ec1c7926da2c6ce","analyzedAt":"2026-08-12T14:44:31.636Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}