Django’s default authentication backend (ModelBackend) expects username as the identifier. However, when a custom user model defines email as USERNAME_FIELD, Django does not recognize it during authentication.
To fix this, a custom authentication backend must be implemented as rightly highlighted by @7berlin above.
Step 1: Create a Custom Authentication Backend
Create a new file inside an appropriate app (e.g., users/auth_backend.py) and add:
from django.contrib.auth.backends import ModelBackend
from django.contrib.auth import get_user_model
UserModel = get_user_model()
class EmailBackend(ModelBackend):
"""
Custom authentication backend to authenticate users using email instead of username.
"""
def authenticate(self, request, email=None, password=None, **kwargs):
if email is None or password is None:
return None
try:
user = UserModel.objects.get(email=email)
except UserModel.DoesNotExist:
return None
if user.check_password(password) and self.user_can_authenticate(user):
return user
return None
Step 2: Register the Custom Authentication Backend
Modify settings.py to tell Django to use this custom authentication backend:
AUTHENTICATION_BACKENDS = [
'users.auth_backend.EmailBackend', # Adjust path based on the project structure
'django.contrib.auth.backends.ModelBackend', # Keep Django's default
]
Why keep ModelBackend?
If Django’s default authentication (e.g., admin login with username) is still required, keeping ModelBackend as a fallback is a good practice.
Step 3: Update the Login Function
Modify the view handling authentication to use the new backend:
from django.contrib.auth import authenticate, login
from django.contrib.auth.models import update_last_login
from rest_framework.response import Response
from rest_framework.decorators import action
from rest_framework.permissions import AllowAny
@action(detail=False, methods=["post"], permission_classes=[AllowAny])
def login(self, request):
email = request.data.get("email")
password = request.data.get("password")
user = authenticate(request, email=email, password=password) # Now works correctly
if user:
login(request, user)
update_last_login(None, user) # Updates last login timestamp
return Response({"message": "Login successful!"})
return Response({"error": "Invalid credentials"}, status=401)
Step 4: Restart the Django Server
After making these changes, restart Django to ensure the new authentication backend is applied:
python manage.py migrate
python manage.py runserver
Expected Outcome:
Additional Debugging Tips
If login still fails, check the following:
This solution was verified with the help of AI, but tested and refined manually.