RocketChat/Rocket.Chat · error · Meteor.Error

error-invalid-user-id

error-invalid-user-id

Error message

Invalid user id

What it means

Thrown by POST /api/v1/users.removeOtherSessions when Users.removeNonPATLoginTokensExcept(userId, hashedToken) returns falsy — i.e. the supplied x-auth-token does not hash-match any persisted non-PAT login token for the user, so the 'keep this session, revoke the rest' operation cannot proceed. Reported as error-invalid-user-id / 'Invalid user id' even though the real problem is the token.

Solutions

  1. Use the authToken from a real login response, not a PAT, for this endpoint
  2. Re-login to obtain a fresh token and retry
  3. Verify the token belongs to x-user-id by calling a cheap authenticated endpoint first
  4. If a PAT-only flow is required, use users.logout with logoutOtherSessions semantics or create a proper session

Example fix

// before
await sdk.post('users.removeOtherSessions', {}, { 'x-auth-token': pat }); // PAT -> always fails
// after
const { data } = await sdk.post('login', { user, password });
await sdk.post('users.removeOtherSessions', {}, { 'x-auth-token': data.authToken });
Defensive patterns

Strategy: validation

Validate before calling

const { data } = await sdk.post('login', { user, password });
if (!data.authToken) throw new Error('login did not yield a resume token');
await sdk.post('users.removeOtherSessions', {}, { 'x-auth-token': data.authToken });

Type guard

async function tokenIsLive(token: string, uid: string): Promise<boolean> {
  const res = await fetch(`${rc}/api/v1/me`, { headers: { 'x-auth-token': token, 'x-user-id': uid } });
  return res.ok;
}

Try / catch

try { await sdk.post('users.removeOtherSessions', {}, h); } catch (e) { if (e.response?.data?.errorType === 'error-invalid-user-id') { await relogin(); await sdk.post('users.removeOtherSessions', {}, freshHeaders); } else throw e; }

Prevention

When it happens

Trigger: Passing a personal access token (PAT) value in x-auth-token (PATs live outside services.resume.loginTokens); passing an already-revoked or expired session token; passing a token belonging to another user.

Common situations: Automation scripts reusing a PAT for a session-scoped endpoint; stale tokens kept in local storage after a password change (which revokes tokens); mixed-up token/userId pairs in multi-account tooling.

Related errors


AI-assisted analysis of RocketChat/Rocket.Chat@e4b8178b20 (2026-08-18). Data as JSON: /api/errors/79b5f1e8ed02fd52. Report an issue: GitHub.

Appendix: source

Thrown at apps/meteor/server/api/v1/users.ts:1681

						tokenExpires: { type: 'string' },
						success: { type: 'boolean', enum: [true] },
					},
					required: ['token', 'tokenExpires', 'success'],
					additionalProperties: false,
				}),
				401: validateUnauthorizedErrorResponse,
			},
		},
		async function action() {
			const xAuthToken = this.request.headers.get('x-auth-token') as string;

			if (!xAuthToken) {
				throw new Meteor.Error('error-parameter-required', 'x-auth-token is required');
			}
			const hashedToken = Accounts._hashLoginToken(xAuthToken);

			if (!(await Users.removeNonPATLoginTokensExcept(this.userId, hashedToken))) {
				throw new Meteor.Error('error-invalid-user-id', 'Invalid user id');
			}

			const me = (await Users.findOneById(this.userId, { projection: { 'services.resume.loginTokens': 1 } })) as Pick<IUser, 'services'>;

			void notifyOnUserChange({
				clientAction: 'updated',
				id: this.userId,
				diff: { 'services.resume.loginTokens': me.services?.resume?.loginTokens },
			});

			const token = me.services?.resume?.loginTokens?.find((token) => token.hashedToken === hashedToken);

			const loginExp = settings.get<number>('Accounts_LoginExpiration');

			const tokenExpires = (token && 'when' in token && new Date(token.when.getTime() + getLoginExpirationInMs(loginExp))) || undefined;

			return API.v1.success({
				token: xAuthToken,

View on GitHub (pinned to e4b8178b20)