sahat/hackathon-starter · error · Error

PROVIDER_COLLISION

PROVIDER_COLLISION

Error message

PROVIDER_COLLISION

What it means

Thrown by handleAuthLogin when a logged-in user attempts to link an OAuth provider account that is already linked to a different local user. The code looks up a User whose provider field equals the provider profile id and compares it to req.user.id; a mismatch means the provider identity belongs to someone else.

Source

Thrown at config/passport.js:84

 *   - Check if it's a returning user.
 *     - If returning user, sign in and we are done.
 *     - Else check if there is an existing account with user's email.
 *       - If there is, return an error message.
 *       - Else create a new account.
 */

/**
 * Helper function that contains the shared post-profile OAuth logic
 * (supports OAuth 1.0a and OAuth 2.0 providers).
 * Returns User (new or updated) on success or throws Error on failure.
 */
async function handleAuthLogin(req, accessToken, refreshToken, providerName, params, providerProfile, sessionAlreadyLoggedIn, tokenSecret, oauth2provider, tokenConfig = {}, refreshTokenExpiration = null) {
  if (sessionAlreadyLoggedIn) {
    const existingUser = await User.findOne({
      [providerName]: { $eq: providerProfile.id },
    });
    if (existingUser && existingUser.id !== req.user.id) {
      throw new Error('PROVIDER_COLLISION');
    }
    let user;
    if (oauth2provider) {
      user = await saveOAuth2UserTokens(req, accessToken, refreshToken, params.expires_in, refreshTokenExpiration, providerName, tokenConfig);
    } else {
      user = await User.findById(req.user.id);
      user.tokens.push({ kind: providerName, accessToken, ...(tokenSecret && { tokenSecret }) });
    }
    user[providerName] = providerProfile.id;
    user.profile.name = user.profile.name || providerProfile.name;
    user.profile.gender = user.profile.gender || providerProfile.gender;

    if (providerProfile.picture) {
      if (!user.profile.pictures || user.profile.pictureSource === undefined) {
        // legacy account (pre-multi-picture support)
        user.profile.pictures = new Map();
        user.profile.picture = providerProfile.picture;
        user.profile.pictureSource = providerName;

View on GitHub (pinned to c12e339564)

Solutions

  1. Log in with the provider account directly instead of linking it, or link the provider from the account that actually owns it
  2. If the provider identity is stale, unlink it from the other account first via /account/unlink/:provider
  3. Inspect the User collection for duplicate provider ids and consolidate accounts manually (mongodb shell)

Example fix

// before: logged in as A, trying to link provider owned by B
// after: check ownership before linking
const owner = await User.findOne({ [provider]: profile.id });
if (owner && owner.id !== req.user.id) {
  req.flash('errors', { msg: 'That account is already linked to another user.' });
  return res.redirect('/account');
}
Defensive patterns

Strategy: validation

Validate before calling

const owner = await User.findOne({ [provider]: providerProfile.id });
if (owner && owner.id !== currentUser.id) {
  // block linking, show explanatory flash message
}

Try / catch

catch (e) { if (e.message === 'PROVIDER_COLLISION') { req.flash('errors', {msg:'That account belongs to another user'}); return res.redirect('/account'); } throw e; }

Prevention

When it happens

Trigger: User A is logged in and visits /auth/<provider> (account-linking flow) while the provider account (e.g. a Google id) is already stored on User B's document; User.findOne({[providerName]: providerProfile.id}) returns B, whose id !== req.user.id.

Common situations: Two team members sharing one OAuth account (shared Twitter/Facebook login), a user who previously created a separate account with the same provider identity, or testing account linking while logged in as a different user.

Related errors


AI-assisted analysis of sahat/hackathon-starter@c12e339564 (2026-08-27). Data as JSON: /api/errors/44e3ef1d1ec7e7db. Report an issue: GitHub.