HuLu ViCa
HuLu ViCa

Reputation: 5450

Django custom authentication back-end doesn't work

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

Answers (4)

nofoobar
nofoobar

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

bob
bob

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

HuLu ViCa
HuLu ViCa

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

ba11b0y
ba11b0y

Reputation: 169

I suppose this might help you. Inheriting from a helper class and making a Custom user is a better approach rather than writing your own custom back end.

Upvotes: 1

Related Questions