On a django-allauth community site, someone signs in with a social provider whose email matches an existing local account; what happens, and how do you control it safely?
answer
- matching by email is takeover-shaped
- off by default
- only provider-verified addresses count
- an adapter hook before login
basics
~10 sBy default allauth does not log the person into the existing account; auto-signup stops on the email conflict. SOCIALACCOUNT_EMAIL_AUTHENTICATION (globally or per provider) enables matching on provider-verified emails, for fully trusted providers only.
solid answer
~40 sallauth first looks for a `SocialAccount` with the same provider and uid. If none exists, it may match by email, but `SOCIALACCOUNT_EMAIL_AUTHENTICATION` defaults to `False`, because an untrustworthy provider could claim anyone's address. With it off, auto-signup (`SOCIALACCOUNT_AUTO_SIGNUP`, default `True`) sees the address is taken under `ACCOUNT_UNIQUE_EMAIL`; depending on enumeration prevention and verification mode the user gets the signup form with the conflict, or a neutral "check your email" page while the owner is notified. Turning it on, ideally per provider under `SOCIALACCOUNT_PROVIDERS`, matches only addresses the provider marked verified; if the local address was unverified, allauth wipes that account's password to lock out a squatter. `SOCIALACCOUNT_EMAIL_AUTHENTICATION_AUTO_CONNECT` also attaches the social account. For custom rules, override `pre_social_login()` on a `DefaultSocialAccountAdapter` subclass.
code
python · 9 linesSOCIALACCOUNT_ADAPTER = "community.adapters.CommunitySocialAdapter"
SOCIALACCOUNT_AUTO_SIGNUP = True
SOCIALACCOUNT_LOGIN_ON_GET = False
SOCIALACCOUNT_EMAIL_AUTHENTICATION = False # global default stays off
SOCIALACCOUNT_EMAIL_AUTHENTICATION_AUTO_CONNECT = True
SOCIALACCOUNT_PROVIDERS = {
"github": {},
"google": {"EMAIL_AUTHENTICATION": True},
}go deeper
Remember that a matching email does not log a social user into an existing account unless you enable it.
Explain the lookup order (social account, then optional email match, then signup) and the settings that control each step.
Describe the takeover risk, per-provider trust, the verified-address requirement, password wiping for squatted accounts, and adapter-based policy.
Decide which providers are trusted identity sources for the community and how account linking and unlinking are governed.
## The situation A community site lets members sign up with a password or with a social provider. Alice registered months ago with `[email protected]` and a password. Today someone clicks "Sign in with" a provider, and the provider reports the address `[email protected]`. Is that Alice? Maybe. It could also be an attacker who controls an account at a lax provider that lets users claim any address. ## How allauth resolves a social login When the provider callback completes, allauth builds a `SocialLogin` and looks up the local user in order: 1. **By social account.** A `SocialAccount` row with the same provider and provider user id means a returning user: log them in. 2. **By email, only if enabled.** If `SOCIALACCOUNT_EMAIL_AUTHENTICATION` (or the provider's own `EMAIL_AUTHENTICATION` key) is on, the adapter's `authenticate_by_email()` takes the addresses the provider marked **verified** and looks for a local user with one of them. 3. **Otherwise, a new user.** allauth tries auto-signup, or shows its social signup form. Before login proceeds, it calls the adapter's `pre_social_login(request, sociallogin)` and sends the `pre_social_login` signal. The adapter hook is the place to intervene, because it runs once in a known order. ## The defaults | Setting | Default | Effect | |---|---|---| | `SOCIALACCOUNT_EMAIL_AUTHENTICATION` | `False` | No login to an existing account by email match | | `SOCIALACCOUNT_EMAIL_AUTHENTICATION_AUTO_CONNECT` | `False` | When email matching is on, do not attach the social account permanently | | `SOCIALACCOUNT_AUTO_SIGNUP` | `True` | Try to create the account from provider data without a form | | `ACCOUNT_UNIQUE_EMAIL` | `True` | Another account may not take the same address | | `SOCIALACCOUNT_LOGIN_ON_GET` | `False` | Starting a social login requires a POST | With the defaults, Alice's address is taken, so **auto-signup is abandoned**. What the visitor sees depends on enumeration prevention: with `ACCOUNT_PREVENT_ENUMERATION` on and mandatory email verification, allauth shows a neutral "verification sent" page and emails the address owner that an account already exists; otherwise it falls back to the signup form, which rejects the taken address. Either way nobody is logged into Alice's account. ## Turning email matching on safely Enable it only for providers you trust to verify addresses, and scope it per provider: ```python SOCIALACCOUNT_PROVIDERS = { "google": { "EMAIL_AUTHENTICATION": True, # per-provider switch }, } SOCIALACCOUNT_EMAIL_AUTHENTICATION_AUTO_CONNECT = True # global setting only ``` Safeguards allauth applies: - **Only verified provider addresses match.** An address the provider did not verify never logs anyone in. - **Squatter lock-out.** If the matching local `EmailAddress` was never verified, someone may have registered it to wait for the real owner. allauth sets that account's password unusable when email authentication logs the real owner in, so the squatter's password stops working. - **Auto-connect is separate, and global.** Unlike email authentication, which can be switched per provider, `SOCIALACCOUNT_EMAIL_AUTHENTICATION_AUTO_CONNECT` is a single project-wide setting. With it `True`, the social account is attached to the local user, so later logins match by social account even if the email changes. With `False`, each login re-matches by email. ## Custom policy with the adapter ```python from allauth.core.exceptions import ImmediateHttpResponse from allauth.socialaccount.adapter import DefaultSocialAccountAdapter from django.shortcuts import render class CommunitySocialAdapter(DefaultSocialAccountAdapter): def pre_social_login(self, request, sociallogin): if sociallogin.is_existing: return if sociallogin.user.email.endswith("@banned.example"): raise ImmediateHttpResponse(render(request, "account/blocked.html")) def is_open_for_signup(self, request, sociallogin): return True # keep social signup open even if password signup closes ``` Register it with `SOCIALACCOUNT_ADAPTER = "community.adapters.CommunitySocialAdapter"`. Raising `ImmediateHttpResponse` aborts the flow with your response; `AccountMiddleware` turns it into the reply. ## What to avoid - Connecting accounts by email yourself in `pre_social_login` without checking the provider's verified flag re-creates the takeover this default exists to prevent. - Enabling `SOCIALACCOUNT_LOGIN_ON_GET` lets a cross-site link start a login handshake; keep POST. - The OAuth mechanics themselves (state, redirect URIs) are the provider integration's concern, not allauth configuration. ## Testing the policy The safest way to keep this behaviour from regressing is to test the three branches explicitly: - A returning social user with an existing `SocialAccount` logs straight in. - A new social user whose provider email matches a local account is **not** logged into it while email authentication is off for that provider. - With email authentication on, a provider address marked unverified does not match, and a matched local account with an unverified address ends up with an unusable password. These tests pin the security decision in code, so a later settings change that widens trust shows up as a failing test rather than a quiet takeover path.
- Why is email matching off by default in allauth when it would be more convenient?Because trust is delegated to the provider. If any enabled provider lets a user attach an address without proving ownership, email matching lets that user sign in as whoever owns the address locally. allauth leaves it off and lets you enable it per provider you have vetted.
- What does SOCIALACCOUNT_EMAIL_AUTHENTICATION_AUTO_CONNECT change?With it on, a successful email match also creates a `SocialAccount` linking the provider identity to the local user, so future logins match by provider uid and survive an email change. With it off the local account is left unchanged, each login re-matches by email, and tokens cannot be stored because no social account row exists.
It is like a club where a guest shows a membership card from a partner club with a member's name on it: the doorman only lets them into that member's locker if the partner club is known to check IDs, and if the locker was never really claimed by its owner, the old padlock is cut off.
saying these in an interview costs you the question
- allauth logs the social user into any local account with the same email by default.
- Email matching is safe with any provider because OAuth proves identity.
- Unverified provider email addresses can be used for email matching.
- The pre_social_login signal is the preferred place to abort the flow.
- Social logins should start on GET so links work everywhere.