Script Valley
Authentication From Scratch
OAuth 2.0 and Social LoginLesson 4.3

How to link social accounts to existing users

account linking, email matching, multiple providers, googleId vs githubId columns, merging accounts, preventing duplicate accounts, conflict resolution

The Account Linking Problem

When a user authenticates with Google, you get a profile with a Google ID and email. Three scenarios are possible, and you must handle all three.

Scenario Handling

async (accessToken, refreshToken, profile, done) => {
  const googleId = profile.id;
  const email = profile.emails[0].value;

  // 1. Check if a user with this Google ID already exists
  let user = await db.findByGoogleId(googleId);
  if (user) return done(null, user);

  // 2. Check if a user with this email exists (but no Google ID yet)
  user = await db.findByEmail(email);
  if (user) {
    // Link the Google account to the existing user
    await db.updateUser(user.id, { googleId });
    return done(null, user);
  }

  // 3. No match — create a new user
  user = await db.createUser({ googleId, email, name: profile.displayName });
  return done(null, user);
}

Security Note on Email Matching

Only auto-link accounts via email if you trust that the OAuth provider has verified the email. Google marks unverified emails — check profile.emails[0].verified. If the email is not verified, do not auto-link. Instead, prompt the user to log in with their existing credentials first, then explicitly link the accounts. Auto-linking unverified emails can be exploited to take over accounts.

Up next

Adding GitHub OAuth login to an Express app

Sign in to track progress