Log in user using either email address or username in Django Log in user using either email address or username in Django python python

Log in user using either email address or username in Django


Yet another solution:

from django.contrib.auth import get_user_modelfrom django.contrib.auth.backends import ModelBackendfrom django.db.models import Qclass EmailOrUsernameModelBackend(ModelBackend):    """    Authentication backend which allows users to authenticate using either their    username or email address    Source: https://stackoverflow.com/a/35836674/59984    """    def authenticate(self, request, username=None, password=None, **kwargs):        # n.b. Django <2.1 does not pass the `request`        user_model = get_user_model()        if username is None:            username = kwargs.get(user_model.USERNAME_FIELD)        # The `username` field is allows to contain `@` characters so        # technically a given email address could be present in either field,        # possibly even for different users, so we'll query for all matching        # records and test each one.        users = user_model._default_manager.filter(            Q(**{user_model.USERNAME_FIELD: username}) | Q(email__iexact=username)        )        # Test whether any matched user has the provided password:        for user in users:            if user.check_password(password):                return user        if not users:            # Run the default password hasher once to reduce the timing            # difference between an existing and a non-existing user (see            # https://code.djangoproject.com/ticket/20760)            user_model().set_password(password)

Fixes:

  • By default, @ is not prohibited in the username field, so unless custom User model prohibits @ symbol, it can't be used to distinguish between username and email.
  • Technically, there can be two users using the same email, one in the email field, the other in the username. Unless such possibility is restricted, it can lead to either user not being able to authenticate, or unhandled MultipleObjectsReturned exception if UserModel._default_manager.get(Q(username__iexact=username) | Q(email__iexact=username)) is used.
  • Catching any exception with except: is generally bad practice

Downside - if there are two users, using the same email, one in the username, the other in email, and they have the same password, then it's prone to authenticating the first match. I guess the chances of this is highly unlikely.

Also note: any of the approaches should enforce unique email field in the User model, since the default User model does not define unique email, which would lead to either unhandled exception in case User.objects.get(email__iexact="...") is used, or authenticating the first match. In any case, using email to login assumes that email is unique.


After following the advice given to me above and changing AUTHENTICATION_BACKENDS = ['yourapp.yourfile.EmailOrUsernameModelBackend'] I was getting the error Manager isn't available; User has been swapped for 'users.User'. This was caused because I was using the default User model instead of my own custom one. Here is the working code.

from django.conf import settingsfrom django.contrib.auth import get_user_modelfrom django.contrib.auth.backends import ModelBackendclass EmailOrUsernameModelBackend(ModelBackend):    """    This is a ModelBacked that allows authentication    with either a username or an email address.        """    def authenticate(self, username=None, password=None):        if '@' in username:            kwargs = {'email': username}        else:            kwargs = {'username': username}        try:            user = get_user_model().objects.get(**kwargs)            if user.check_password(password):                return user        except User.DoesNotExist:            return None    def get_user(self, username):        try:            return get_user_model().objects.get(pk=username)        except get_user_model().DoesNotExist:            return None


I thought I'd chuck my simpler approach in for anyone else who comes across this:

# -*- coding: utf-8 -*-from django.contrib.auth import backends, get_user_modelfrom django.db.models import Qclass ModelBackend(backends.ModelBackend):    def authenticate(self, username=None, password=None, **kwargs):        UserModel = get_user_model()        try:            user = UserModel.objects.get(Q(username__iexact=username) | Q(email__iexact=username))            if user.check_password(password):                return user        except UserModel.DoesNotExist:            # Run the default password hasher once to reduce the timing            # difference between an existing and a non-existing user (#20760).            UserModel().set_password(password)

Note:

  • disregards USERNAME_FIELD, although you could add it back in pretty easily
  • case insensitive (you could just remove the __iexact's though to make it not)