如何让AllAuth仅允许单表登录并保持邮箱数据一致性?
Great question! When using a custom User model with AllAuth, it's super common to hit this consistency snag between User.email and AccountEmailAddress.email—especially since AllAuth defaults to checking both tables for login credentials. Here's a clean, maintainable approach to keep your data in sync and restrict login to just one source (your User model's email field):
Step 1: Sync Emails Bidirectionally with Signals
To ensure both tables always have the same primary email, use Django signals to automatically sync updates in either direction. This avoids manual updates in views (which are error-prone if you miss any update paths like admin edits or API calls).
Create a signals.py file in your app:
from django.db.models.signals import post_save from django.dispatch import receiver from django.contrib.auth import get_user_model from allauth.account.models import EmailAddress User = get_user_model() # Sync User.email to AccountEmailAddress when a user is created or their email changes @receiver(post_save, sender=User) def sync_user_to_emailaddress(sender, instance, created, **kwargs): if created: # Auto-create a verified primary EmailAddress record for new users EmailAddress.objects.get_or_create( user=instance, email=instance.email, defaults={"verified": True, "primary": True} ) else: # Update the primary EmailAddress if the User's email was modified primary_email, _ = EmailAddress.objects.get_or_create(user=instance, primary=True) if primary_email.email != instance.email: primary_email.email = instance.email # Mark as verified if you trust the update; set to False if you need re-verification primary_email.verified = True primary_email.save(update_fields=["email", "verified"]) # Sync AccountEmailAddress back to User.email if the primary email is updated via AllAuth @receiver(post_save, sender=EmailAddress) def sync_emailaddress_to_user(sender, instance, **kwargs): if instance.primary and instance.email != instance.user.email: instance.user.email = instance.email instance.user.save(update_fields=["email"])
Register the signals in your app's apps.py to activate them:
from django.apps import AppConfig class YourAppConfig(AppConfig): default_auto_field = "django.db.models.BigAutoField" name = "your_app_name" # Replace with your actual app name def ready(self): import your_app_name.signals # Match the path to your signals file
Step 2: Restrict Login to Only the User Model's Email
By default, AllAuth's authentication backend checks both AccountEmailAddress and User for valid login details. To override this, create a custom backend that only validates against your User model:
Create a backends.py file in your app:
from django.contrib.auth.backends import ModelBackend from django.contrib.auth import get_user_model User = get_user_model() class UserEmailBackend(ModelBackend): def authenticate(self, request, email=None, password=None, **kwargs): try: # Fetch the user directly from your custom User model using email user = User.objects.get(email=email) # Validate the password and ensure the user is active if user.check_password(password) and self.user_can_authenticate(user): return user except User.DoesNotExist: # Return None to let other backends handle authentication (if needed) return None
Update your settings.py to use this custom backend:
AUTHENTICATION_BACKENDS = [ "your_app_name.backends.UserEmailBackend", # Replace with your app's path # Keep this line if you need AllAuth's social login functionality # "allauth.account.auth_backends.AuthenticationBackend", ]
Step 3: Optional - Disable Multi-Email Support
If you don't want users to add secondary emails, enforce a single email limit in settings.py to prevent extra entries in the AccountEmailAddress table:
ACCOUNT_MAX_EMAIL_ADDRESSES = 1
Why This Is Better Than View-Level Updates
Your initial idea of updating AccountEmailAddress in views works for specific cases, but it's not scalable. Signals ensure sync happens automatically no matter where the email is updated (admin panel, user profile page, API, etc.). Using a custom backend also keeps your login logic focused on your User model, eliminating confusion from AllAuth's default dual-table checks.
内容的提问来源于stack exchange,提问作者Kowalski

