Reputation: 5450
I want my Django project to authenticate users with their email instead of their user-name. I followed this suggestion, but it doesn't work.
This is my EmailBackend
from django.contrib.auth import get_user_model
from django.contrib.auth.backends import ModelBackend
class EmailBackend(ModelBackend):
def authenticate(self, username=None, password=None, **kwars):
UserModel = get_user_model()
try:
user = UserModel.objects.get(email=username)
except UserModel.DoesNotExist:
return None
else:
if user.check_password(password):
return user
return None
I have added AUTHENTICATION_BACKENDS = ['GeneralApp.utils.EmailBackend']
to settings.py and, when it is commented, I can login using user-name, and when I uncomment it, I cannot login anymore. I have traced out that the code of GeneralApp.utils.EmailBackend.authenticate()
is never executed, but I know Django can locate the class because I have misspelled it intentionally and I get an error, and when I correct the typo, no exception is raised. I also know that the default authentication backend was effectively override because I cannot neither login with user-name. I have this same solution working perfectly in another project so I cannot understand why the custom authentication code is never executed when the class is perfectly located with AUTHENTICATION_BACKENDS
set to it.
Upvotes: 13
Views: 6954
Reputation: 3215
Working in Django 3.x
Custom Authentication file would look like:
from django.contrib.auth import get_user_model
User = get_user_model()
class EmailAuthBackend(object):
"""It provides the functionality to slide down to email to login,
instead of just username"""
def authenticate(self,request,username=None,password=None):
try:
user = User.objects.get(email=username)
if user.check_password(password): #you can also test user.is_active
return user
return None
except User.DoesNotExist:
return None
def get_user(self,user_id):
try:
return User.objects.get(id=user_id)
except User.DoesNotExist:
return None
and in the settings.py we need to put:
#Custom Authentication Backend
AUTHENTICATION_BACKENDS = [
'django.contrib.auth.backends.ModelBackend',
'path_to_custom_backend.EmailAuthBackend',
]
Upvotes: 5
Reputation: 751
Django 2.1 - 4.0
When creating an authentication backend it is best to follow the example of ModelBackend
and run the default password hasher.
The following backend will authenticate using emails as well as usernames.
# backends.py
from django.contrib.auth import get_user_model
from django.contrib.auth.backends import ModelBackend
from django.db.models import Q
UserModel = get_user_model()
class CustomModelBackend(ModelBackend):
def authenticate(self, request, username=None, password=None, **kwargs):
if username is None:
username = kwargs.get(UserModel.USERNAME_FIELD, kwargs.get(UserModel.EMAIL_FIELD))
if username is None or password is None:
return
try:
user = UserModel._default_manager.get(
Q(username__exact=username) | Q(email__iexact=username)
)
except UserModel.DoesNotExist:
# Run the default password hasher once to reduce the timing
# difference between an existing and a nonexistent user (#20760).
UserModel().set_password(password)
else:
if user.check_password(password) and self.user_can_authenticate(user):
return user
Be sure to add your backend to settings.py
# settings.py
AUTHENTICATION_BACKENDS = ["path.to.backends.CustomModelBackend"]
Upvotes: 1
Reputation: 5450
Ok, I have tried this before with Django 2.0.5, but it stopped working with Django 2.1. I researched here and found that custom authentication backend class now expects parameter request in method authenticate. So the final code for Django 2.1 is:
class EmailBackend(ModelBackend):
def authenticate(self, request, username=None, password=None, **kwars):
UserModel = get_user_model()
try:
user = UserModel.objects.get(email=username)
except UserModel.DoesNotExist:
return None
else:
if user.check_password(password):
return user
return None
Upvotes: 34