RocketChat/Rocket.Chat · error · Meteor.Error
401
401
Error message
no matching login attempt found
What it means
During CAS login the handler consumes the one-time cas.credentialToken via CredentialTokens.removeNotExpiredById. If no matching, unexpired token document exists, it throws Meteor.Error with Accounts.LoginCancelledError.numericError (401) and the message 'no matching login attempt found' — the standard signal that the temporary credential for this login attempt is already gone.
Source
Thrown at apps/meteor/server/lib/cas/loginHandler.ts:19
import { CredentialTokens, Users } from '@rocket.chat/models';
import { getObjectKeys, wrapExceptions } from '@rocket.chat/tools';
import { Accounts } from 'meteor/accounts-base';
import { Meteor } from 'meteor/meteor';
import { createNewUser } from './createNewUser';
import { findExistingCASUser } from './findExistingCASUser';
import { logger } from './logger';
import { settings } from '../../settings';
import { setRealName } from '../users/setRealName';
export const loginHandlerCAS = async (options: any): Promise<undefined | Accounts.LoginMethodResult> => {
if (!settings.get('CAS_enabled') || !options.cas || typeof options.cas.credentialToken !== 'string') {
return undefined;
}
const credentials = await CredentialTokens.removeNotExpiredById(options.cas.credentialToken);
if (credentials === undefined || credentials === null) {
throw new Meteor.Error(Accounts.LoginCancelledError.numericError, 'no matching login attempt found');
}
const result = credentials.userInfo;
const syncUserDataFieldMap = settings.get<string>('CAS_Sync_User_Data_FieldMap').trim();
const casVersion = parseFloat(settings.get('CAS_version') ?? '1.0');
const syncEnabled = settings.get('CAS_Sync_User_Data_Enabled');
const flagEmailAsVerified = settings.get<boolean>('Accounts_Verify_Email_For_External_Accounts');
const userCreationEnabled = settings.get('CAS_Creation_User_Enabled');
const { username, attributes: credentialsAttributes } = result as { username: string; attributes: Record<string, string[]> };
// We have these
const externalAttributes: Record<string, string> = {
username,
};
// We need these
const internalAttributes: Record<string, string | undefined> = {View on GitHub (pinned to b2c16d5842)
Solutions
- Go back to the login page and start the CAS login again to obtain a fresh credential token.
- Never refresh, bookmark, or replay the CAS callback URL.
- Check CAS service-ticket/token lifetimes on the IdP and reduce latency between redirect and callback.
- Ensure ROOT_URL and the CAS service URL registered on the IdP match exactly, and that server clocks are NTP-synced.
Defensive patterns
Strategy: retry
Try / catch
import { Accounts } from 'meteor/accounts-base';
try {
await Meteor.loginWithCAS(casToken);
} catch (e) {
if (e instanceof Meteor.Error && e.error === Accounts.LoginCancelledError.numericError) {
// one-shot credential consumed/expired: send the user back to the login page for a fresh attempt
}
throw e;
} Prevention
- Treat CAS credential tokens as one-shot: never refresh or replay the callback URL.
- Start each login attempt from the login page so a fresh credentialToken is generated.
- NTP-sync all servers and keep IdP ticket lifetimes comfortably above real redirect latency.
When it happens
Trigger: The CAS callback arrives with a credentialToken that was already consumed (page refresh, double callback submission), expired before the callback landed (slow IdP or network, clock skew), or never existed (stale/hand-crafted login URL).
Common situations: Users refreshing the CAS callback page; retrying a failed login from an old page; long gaps between CAS redirect and callback; servers with unsynchronized clocks; multiple tabs starting CAS logins at once.
Related errors
- Service not configured
- registration-disabled-authentication-services
- error-invalid-state
- error-invalid-user
- error-invalid-username
AI-assisted analysis of RocketChat/Rocket.Chat@b2c16d5842 (2026-08-18).
Data as JSON: /api/errors/c89347b15950751f.
Report an issue: GitHub.