Prompt
How do I prevent duplicate accounts with OAuth sign-in?
Latest observation
To prevent duplicate accounts with OAuth sign-in, make one user identity in your database and link OAuth identities to it instead of creating a new account every time.
Core approach
Use this rule:
- One app user = one internal
user_id - Each OAuth login provider account = a linked identity, e.g.:
- Google
sub - GitHub
id - Microsoft
oid
- Google
- Store those identities in a separate table and tie them to the same user.
Recommended database model
users
Your app’s canonical user record:
idemailnamecreated_at
oauth_accounts
Linked login identities:
iduser_id(FK tousers.id)provider(google,github, etc.)provider_user_id(the unique ID from the provider)provider_emailcreated_at
Add a unique constraint on:
(provider, provider_user_id)
Optionally also on:
emailinusersif your app requires one account per email
Sign-in flow
When a user signs in with OAuth:
- Identify provider account
- Read the provider’s unique user ID.
- Check if this OAuth identity already exists
- If yes, log in the linked
user_id.
- If yes, log in the linked
- If not, check for an existing user by verified email
- Only if the provider gives a verified email.
- If matching user exists, link the new OAuth account
- Add a row in
oauth_accounts.
- Add a row in
- Otherwise create a new user
- Create
users, then createoauth_accounts.
- Create
Important anti-duplication rules
1. Match on provider user ID, not email alone
Emails can change, be shared, or be unverified. The provider’s unique ID is the real stable identifier.
2. Only auto-link by email if it is verified
Otherwise someone could claim another person’s email and get linked incorrectly.
3. Enforce uniqueness in the database
Application logic alone is not enough. Add constraints to prevent race conditions.
4. Support account linking
Let users connect multiple OAuth providers to the same account after login.
Good linking strategy
If a user signs in with Google and later GitHub:
- If Google account already exists in
oauth_accounts, return the sameuser_id - If GitHub login has the same verified email, you may:
- auto-link if policy allows, or
- require the user to be signed in first and explicitly link GitHub
The safest approach is:
- Auto-link only when provider says email is verified
- Otherwise require explicit account linking
Example logic
Pseudo-flow:
oauth_callback(provider, provider_user_id, email, email_verified):
if oauth_account exists for (provider, provider_user_id):
sign in as oauth_account.user_id
else if email_verified and user exists with email:
create oauth_account linked to that user
sign in as that user
else:
create new user
create oauth_account linked to new user
sign in as new user
Best practice
If your app supports email/password too:
- Use the same
userstable for both password and OAuth users - Store password hash only for users who use passwords
- Treat OAuth identities as login methods, not separate accounts
Security note
Never allow account linking based only on:
- unverified email
- display name
- profile picture
- username
These are not reliable identity proofs.
If you want, I can also give you:
- a SQL schema
- an implementation example in Node/Express, Django, Rails, or Laravel
- a NextAuth/Auth0/Passport.js setup to avoid duplicates
Brands