makeplane/plane · critical · AppError
AUTH_USER_MISMATCH
AUTH_USER_MISMATCH
Error message
Authentication unsuccessful: User ID mismatch
What it means
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.
Source
Thrown at apps/live/src/lib/auth.ts:81
context.cookie = cookie ?? requestParameters.get("cookie") ?? "";
context.documentType = requestParameters.get("documentType")?.toString() as TDocumentTypes;
context.projectId = requestParameters.get("projectId");
context.userId = userId;
context.workspaceSlug = requestParameters.get("workspaceSlug");
return await handleAuthentication({
cookie: context.cookie,
userId: context.userId,
});
};
export const handleAuthentication = async ({ cookie, userId }: { cookie: string; userId: string }) => {
// fetch current user info
try {
const userService = new UserService();
const user = await userService.currentUser(cookie);
if (user.id !== userId) {
throw new AppError("Authentication unsuccessful: User ID mismatch", { code: "AUTH_USER_MISMATCH" });
}
return {
user: {
id: user.id,
name: user.display_name,
},
};
} catch (error) {
const appError = new AppError(error, {
context: { operation: "handleAuthentication" },
});
logger.error("Authentication failed", appError);
throw new AppError("Authentication unsuccessful", { code: appError.code });
}
};
View on GitHub (pinned to 1c8a60f858)
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.
Example fix
// before: client stores token once and never refreshes it
const token = JSON.stringify({ id: cachedUserId, cookie: cachedCookie });
// after: always derive the token from the live session right before connecting
const me = await api.get('/users/me/');
const token = JSON.stringify({ id: me.id, cookie: document.cookie }); Defensive patterns
Strategy: validation
Validate before calling
// Client-side, before opening the websocket: confirm token id matches the live session
const me = await fetch('/api/users/me/', { credentials: 'include' }).then(r => r.ok ? r.json() : null);
const tokenPayload = JSON.parse(storedToken);
if (!me || me.id !== tokenPayload.id) {
// refresh the token pair before connecting
await refreshSession();
} Type guard
const isUserDetailsToken = (t: unknown): t is { id: string; cookie: string } =>
typeof t === 'object' && t !== null &&
typeof (t as any).id === 'string' &&
typeof (t as any).cookie === 'string'; Try / catch
try { await handleAuthentication({ cookie, userId }); }
catch (e) {
if (e instanceof AppError && e.code === 'AUTH_USER_MISMATCH') {
// force re-login: token does not match session
await signOutAndRedirect();
} else throw e;
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Understand the failure class
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- AUTH_MISSING_CREDENTIALS
- Authentication unsuccessful
- Given API token is not valid
- Given API token is not valid
- 5005
AI-assisted analysis of makeplane/plane@1c8a60f858 (2026-08-12).
Data as JSON: /api/errors/55e692706f4d2bbb.
Report an issue: GitHub.