{"record":{"id":"2efc7817ab2e2371","repo":"RocketChat/Rocket.Chat","slug":"customoauth-2efc78","errorCode":"CustomOAuth","errorMessage":"User with username ${user.username} already exists","messagePattern":"User with username (.+?) already exists","errorType":"exception","errorClass":"Meteor.Error","httpStatus":null,"severity":"error","filePath":"apps/meteor/server/lib/auth-providers/custom-oauth/custom_oauth_server.js","lineNumber":416,"sourceCode":"\t\t\t\tif (!user) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tawait callbacks.run('afterProcessOAuthUser', { serviceName, serviceData, user });\n\n\t\t\t\t// User already created or merged and has identical name as before\n\t\t\t\tif (\n\t\t\t\t\tuser.services &&\n\t\t\t\t\tuser.services[serviceName] &&\n\t\t\t\t\tuser.services[serviceName].id === serviceData.id &&\n\t\t\t\t\tuser.name === serviceData.name &&\n\t\t\t\t\t(this.keyField === 'email' || !serviceData.email || user.emails?.find(({ address }) => address === serviceData.email))\n\t\t\t\t) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (this.mergeUsers !== true) {\n\t\t\t\t\tthrow new Meteor.Error('CustomOAuth', `User with username ${user.username} already exists`);\n\t\t\t\t}\n\n\t\t\t\tconst serviceIdKey = `services.${serviceName}.id`;\n\t\t\t\tconst successCallbacks = [\n\t\t\t\t\tasync () => {\n\t\t\t\t\t\tconst updatedUser = await Users.findOneById(user._id, { projection: { name: 1, emails: 1, [serviceIdKey]: 1 } });\n\t\t\t\t\t\tif (updatedUser) {\n\t\t\t\t\t\t\tconst { _id, ...diff } = updatedUser;\n\t\t\t\t\t\t\tvoid notifyOnUserChange({ clientAction: 'updated', id: user._id, diff });\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t];\n\n\t\t\t\tconst session = client.startSession();\n\t\t\t\ttry {\n\t\t\t\t\t// Extend the session to match the ExtendedSession type expected by saveUserIdentity\n\t\t\t\t\tObject.assign(session, {\n\t\t\t\t\t\tonceSuccesfulCommit: (cb) => {","sourceCodeStart":398,"sourceCodeEnd":434,"githubUrl":"https://github.com/RocketChat/Rocket.Chat/blob/b2c16d5842cbe6b69b59bdf6fc5e5f1afcd1f0b0/apps/meteor/server/lib/auth-providers/custom-oauth/custom_oauth_server.js#L398-L434","documentation":"Legacy (deprecated, non-Passport) CustomOAuth class twin of the account-collision error: the pre-login hook found an existing user whose username (keyField 'username') or e-mail (keyField 'email') matches the OAuth identity, the user is not already linked to this service id (or their data changed), and mergeUsers !== true. Thrown as Meteor.Error('CustomOAuth', 'User with username <u> already exists') to prevent an SSO identity from silently taking over a local account.","triggerScenarios":"keyField 'username' and the OAuth username matches a pre-existing local user not yet linked to this service; keyField 'email' and the OAuth e-mail matches another account; a returning linked user whose provider-side name changed so the 'identical data' early-return no longer applies while mergeUsers is disabled.","commonSituations":"Password-registered users trying SSO for the first time after SSO rollout; two IdPs sharing usernames/e-mails; admin never enabled 'Merge users'; usernameField mapped to a non-unique claim.","solutions":["Enable 'Merge users' (mergeUsers option) in the custom OAuth configuration so the identity links into the existing account","Rename the conflicting local user so the OAuth username no longer collides","Map usernameField to a unique claim (sub, preferred_username) and set keyField deliberately","Enable mergeUsersDistinctServices when multiple providers legitimately share usernames"],"exampleFix":"// before\nnew CustomOAuth('keycloak', { serverURL, mergeUsers: false, ...opts });\n// -> Meteor.Error CustomOAuth: User with john.doe already exists\n\n// after\nnew CustomOAuth('keycloak', { serverURL, mergeUsers: true, ...opts });","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n  await Accounts.updateOrCreateUserFromExternalService(serviceName, serviceData, options);\n} catch (error) {\n  if (error instanceof Meteor.Error && error.error === 'CustomOAuth' && /already exists/.test(error.reason)) {\n    throw new Meteor.Error('custom-oauth-conflict', 'Username already taken by a local account; merge or rename required.');\n  }\n  throw error;\n}","preventionTips":["Enable mergeUsers before SSO rollout on workspaces with existing password users","Map usernameField to unique claims (sub, preferred_username)","Keep keyField stable once chosen","Use the error as a signal to run an account-merge cleanup, not to disable SSO"],"tags":["oauth","custom-oauth","account-linking","user-conflict","deprecated"],"backgroundTag":"oauth-account-linking-conflict","analyzedSha":"b2c16d5842cbe6b69b59bdf6fc5e5f1afcd1f0b0","analyzedAt":"2026-08-18T15:26:39.429Z","schemaVersion":2},"datasetVersion":"2026-08-21T18:17:14.833Z"}