[Django]-Log in user using either email address or username in Django

21๐Ÿ‘

โœ…

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 settings
from django.contrib.auth import get_user_model
from django.contrib.auth.backends import ModelBackend

class 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
๐Ÿ‘คuser3282276

33๐Ÿ‘

Yet another solution:

from django.contrib.auth import get_user_model
from django.contrib.auth.backends import ModelBackend
from django.db.models import Q


class 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.

๐Ÿ‘ค1bit0fMe

9๐Ÿ‘

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_model
from django.db.models import Q


class 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)
๐Ÿ‘คSteve

6๐Ÿ‘

I know this is already answered, however I have found a real neat way to implement login with both e-mail and username using the Django auth views. I did not see anyone use this type of method so I thought Iโ€™d share it for simplicityโ€™s sake.

from django.contrib.auth.models import User


class EmailAuthBackend():
    def authenticate(self, username=None, password=None):
        try:
            user = User.objects.get(email=username)
            if user.check_password(raw_password=password):
                return user
            return None
        except User.DoesNotExist:
            return None

    def get_user(self, user_id):
        try:
            return User.objects.get(pk=user_id)
        except User.DoesNotExist:
            return None

Then in your settings.py add this

AUTHENTICATION_BACKENDS = (
    'django.contrib.auth.backends.ModelBackend',
    'myapp.authentication.EmailAuthBackend',
)
๐Ÿ‘คex8

6๐Ÿ‘

I wrote the code for this in 2 simple steps :

  1. VIEWS.py
     if request.method == 'POST':
            userinput = request.POST['username']

            try:
                username = userbase.objects.get(email=userinput).username
            except userbase.DoesNotExist:
                username = request.POST['username']
            password = request.POST['password']
  1. INDEX.html

    I created 2 input fields from which 1st is for username/email. I take whatever input is given and try to search the same data in email column of db, if it matches, I return the username and then try to authenticate , if it doesnโ€™t , I use input directly as Username.

Iโ€™m using Django 2.2

๐Ÿ‘คkrishna lodha

5๐Ÿ‘

You can simply have a Try block to do this..
use:

email = request.POST['username'] 
raw_password = request.POST['password'] 

   try:
        account = authenticate(username=MyUserAccount.objects.get(email=email).username,password=raw_password)
        if account is not None:
            login(request, account)
            return redirect('home')

    except:
        account = authenticate(username=email, password=raw_password)
        if account is not None:
            login(request, account)
            return redirect('home')
๐Ÿ‘คm4gn8o

2๐Ÿ‘

Updated version of the same snippet, with improved security. Also, it allow you to enable or disable case sensitive authentication. If you prefer, you can install it directly from pypi.

from django.contrib.auth.backends import ModelBackend
from django.contrib.auth import get_user_model
from django.conf import settings

###################################
"""  DEFAULT SETTINGS + ALIAS   """
###################################


try:
    am = settings.AUTHENTICATION_METHOD
except:
    am = 'both'
try:
    cs = settings.AUTHENTICATION_CASE_SENSITIVE
except:
    cs = 'both'

#####################
"""   EXCEPTIONS  """
#####################


VALID_AM = ['username', 'email', 'both']
VALID_CS = ['username', 'email', 'both', 'none']

if (am not in VALID_AM):
    raise Exception("Invalid value for AUTHENTICATION_METHOD in project "
                    "settings. Use 'username','email', or 'both'.")

if (cs not in VALID_CS):
    raise Exception("Invalid value for AUTHENTICATION_CASE_SENSITIVE in project "
                    "settings. Use 'username','email', 'both' or 'none'.")

############################
"""  OVERRIDDEN METHODS  """
############################


class DualAuthentication(ModelBackend):
    """
    This is a ModelBacked that allows authentication
    with either a username or an email address.
    """

    def authenticate(self, username=None, password=None):
        UserModel = get_user_model()
        try:
            if ((am == 'email') or (am == 'both')):
                if ((cs == 'email') or cs == 'both'):
                    kwargs = {'email': username}
                else:
                    kwargs = {'email__iexact': username}

                user = UserModel.objects.get(**kwargs)
            else:
                raise
        except:
            if ((am == 'username') or (am == 'both')):
                if ((cs == 'username') or cs == 'both'):
                    kwargs = {'username': username}
                else:
                kwargs = {'username__iexact': username}

                user = UserModel.objects.get(**kwargs)
        finally:
            try:
                if user.check_password(password):
                    return user
            except:
                # Run the default password hasher once to reduce the timing
                # difference between an existing and a non-existing user.
                UserModel().set_password(password)
                return None

    def get_user(self, username):
        UserModel = get_user_model()
        try:
            return UserModel.objects.get(pk=username)
        except UserModel.DoesNotExist:
            return None
๐Ÿ‘คAdrian Lopez

2๐Ÿ‘

I tried to go through the Django library to know how they authenticated the admin user, Iโ€™ve found some piece of code from the Django library authentication class the code which worked for me Iโ€™m attaching here.

Create a yourFavNameAuthenticate.py file in your app
and paste this code there

yourFavNameAuthenticate.py

from django.db.models import Q

from django.contrib.auth import get_user_model

UserModel = get_user_model()


class UsernameOrEmailBackend(object):
    def authenticate(self, request, username=None, password=None, **kwargs):
        if username is None:
            username = kwargs.get(UserModel.USERNAME_FIELD)
        if username is None or password is None:
            return
        try:
            user = UserModel.objects.get(
                Q(username=username) | Q(email=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

    def user_can_authenticate(self, user):
        """
        Reject users with is_active=False. Custom user models that don't have
        that attribute are allowed.
        """
        is_active = getattr(user, 'is_active', None)
        return is_active or is_active is None

    def get_user(self, user_id):
        try:
            user = UserModel._default_manager.get(pk=user_id)
        except UserModel.DoesNotExist:
            return None
        return user if self.user_can_authenticate(user) else None

Go to settings.py and paste a single line which will tell where to look for authentication files

AUTHENTICATION_BACKENDS = (
    'django.contrib.auth.backends.ModelBackend', #<- This is the Django's authentication class object
    'appName.yourFavNameAuthenticate.UsernameOrEmailBackend', #<-This is yourFavNameAuthenticate.py class object
)
๐Ÿ‘คSaroj

1๐Ÿ‘

if you are using django-rest-auth then the option to authenticate with email address is built in, and may conflict with the other methods proposed. You just need to add the following to settings.py:

ACCOUNT_AUTHENTICATION_METHOD = 'username_email'
ACCOUNT_EMAIL_REQUIRED = False
ACCOUNT_USERNAME_REQUIRED = False

#Following is added to enable registration with email instead of username
AUTHENTICATION_BACKENDS = (
 # Needed to login by username in Django admin, regardless of `allauth`
 "django.contrib.auth.backends.ModelBackend",

 # `allauth` specific authentication methods, such as login by e-mail
 "allauth.account.auth_backends.AuthenticationBackend",
)

Django rest auth email instead of username
https://django-allauth.readthedocs.io/en/latest/configuration.html

Note that unless you want to have a single box into which the user can type a username or an email address, youโ€™ll have to do some work in the front end to decide whether to send the login request as email, password or username, password. I did a simple test whether the userโ€™s entry contained an โ€˜@โ€™ with a โ€˜.โ€™ further on. I think somebody deliberately creating a username that looks like an email address โ€“ but isnโ€™t their email address โ€“ is unlikely enough that Iโ€™m not supporting it.

๐Ÿ‘คLittle Brain

1๐Ÿ‘

Those who are still struggling. Because of following multiple tutorials, we end up with this kind of a mess. Actually there are multiple ways to create login view in Django. I was kinda mixing these solutions in my Django predefined method of log in using

def login_request(request):
    form = AuthenticationForm()
    return render(request = request,
              template_name = "main/login.html",
              context={"form":form})

But now I have worked around this problem by using this simple approach without modifying the authentication backend.

In root directory (mysite) urls.py

urlpatterns = [
    path('admin/', admin.site.urls),
    path('accounts/', include('django.contrib.auth.urls')),
]

In your app directory urls.py

urlpatterns = [
    path('accounts/login/', views.login_view, name='login'),
]

In views.py

def login_view(request):
    if request.method == 'POST':
        userinput = request.POST['username']
        try:
            username = User.objects.get(email=userinput).username
        except User.DoesNotExist:
            username = request.POST['username']
        password = request.POST['password']
        user = auth.authenticate(username=username, password=password)

        if user is not None:
            auth.login(request, user)
            messages.success(request,"Login successfull")
            return redirect('home')
        else:
            messages.error(request,'Invalid credentials, Please check username/email or password. ')
    return render(request, "registration/login.html")

Finally, In templates (registration/login.html)

<div class="container ">
  <h2>SIGN IN</h2>
    <form action="{% url 'login' %}" method="post">
      {% csrf_token %}
      <div class="form-group">
        <label for="Password">Username or Email:</label>
        <input type="text" class="form-control" placeholder="Enter User Name" name="username">
      </div>
       <div class="form-group">
        <label for="Password">Password:</label>
        <input type="password" class="form-control"  placeholder="Enter Password" name="password">
      </div>
      <button type="submit" class="btn btn-dark">Sign In</button> &ensp; <a  class="" href="{% url 'password_reset' %}">
        Forgot Password?
        </a>
      <p>Don't have an account? <a  class="" href="{% url 'blog:signup' %}">
        REGISTER NOW
        </a></p> 
    </form>
</div>

This is the easiest solution I came up with.

๐Ÿ‘คMayank chauhan

1๐Ÿ‘

To keep things as simple as possible, you could just add a quick check to ModelBackend and alter the username passed if an email on record matches.

In this way, you minimize the effect you are having on the ModelBackend.

#myapp.auth.py
from django.contrib.auth.backends import ModelBackend, UserModel

class EmailThenUsernameModelBackend(ModelBackend):
    """
    Overwrites functionaility for ModelBackend related to authenticate function
    """
    def authenticate(self, request, username=None, password=None, **kwargs):
        """
        Check if passed username matches an email on record
            If so: override the email with the username on record
            Else: pass the username as is
        """
        user=UserModel._default_manager.filter(email=username).first()
        if user:
            username=user.username
        return super().authenticate(request, username, password, **kwargs)
# myapp.settings.py 
...
AUTHENTICATION_BACKENDS = ['myapp.auth.EmailThenUsernameModelBackend']

By default django does not force emails to be unique. You could simply add this to a custom user model:

# myapp.models.py
class CustomUser(AbstractUser):
    # Overwrite email field at django.contrib.auth.models.AbstractUser
    # Force email field to exist and to be unique
    email = models.EmailField(
        _('email address'),
        unique=True
    )
  • Note: This does not include the extra steps needed to get the custom user setup.
๐Ÿ‘คconmak

0๐Ÿ‘

Hereโ€™s a work-around that doesnโ€™t require modifying the authentication backend at all.

First, look at the example login view from Django.

from django.contrib.auth import authenticate, login

def my_view(request):
    username = request.POST['username']
    password = request.POST['password']
    user = authenticate(username=username, password=password)
    if user is not None:
        login(request, user)
        # Redirect to a success page.
        ...
    else:
        # Return an 'invalid login' error message.
        ...

If authentication with the username fails we can check if there is an email match, get the corresponding username, and try to authenticate again.

from django.contrib.auth import authenticate, login, get_user_model

def my_view(request):
    username = request.POST['username']
    password = request.POST['password']
    user = authenticate(username=username, password=password)
    if user is None:
        User = get_user_model()
        user_queryset = User.objects.all().filter(email__iexact=username)
        if user_queryset:
            username = user_queryset[0].username
            user = authenticate(username=username, password=password)
    if user is not None:
        login(request, user)
        # Redirect to a success page.
        ...
    else:
        # Return an 'invalid login' error message.
        ...

Similar to 1bit0fMeโ€™s example, email should be a unique field and there is the same (highly unlikely) downside that they mentioned.

I would only recommend this approach if all login on your site is handled by a single view or form. Otherwise, it would be better to modify the authenticate() method itself in the backend to avoid creating multiple points of potential failure.

๐Ÿ‘คJohn Meinken

0๐Ÿ‘

Assuming you have blocked/forbidden against the username having an @, and you want to use the django User model.

if request.method == 'POST':
    form = LoginForm(request.POST)
    if form.is_valid():
        cd=form.cleaned_data
        if '@' in cd['username']:
            username=User.objects.get(email=cd['username']).username
        else:
            username=cd['username']

        user = authenticate(username=username,
                                password=cd['password'])

        if user is not None and user.is_active:
            login(request,user)
            return redirect('loggedin')
        else:
            return render(request, 'login.html')

0๐Ÿ‘

Note that for the most solutions like these you should add email uniqueness validation for User model to avoid the authentication vulnerability

# models.py
from django.contrib.auth.models import AbstractUser


class User(AbstractUser):
    objects = UserManager()
    email = models.EmailField(_('email address'), unique=True)

    class Meta:
        verbose_name = _('user')
        verbose_name_plural = _('users')
        db_table = 'auth_user'
        swappable = 'AUTH_USER_MODEL'

and then you have to update settings.py defining the AUTH_USER_MODEL property

AUTH_USER_MODEL = '[your_app_name].User'
๐Ÿ‘คdtar

0๐Ÿ‘

I implemented the solution in my view. However, my users are not allowed to have emails as username during registration and also each email is unique.

if request.method=="POST":  
    username = request.POST.get('username').lower()
    password = request.POST.get('password')
    '''check if the username is a valid email'''
    try:
        email = validate_email(username)
        username = User.objects.get(email=username).username
    except:
        pass
    user = authenticate(request, username=username, password=password)
    if user is not None:
        login(request,user)
    else:
        messages.error(request,("Error logging in."))
        return redirect('login')

I am using validate_email so that my users can have @ in their usernames, @bestuser is a valid username but not a valid email. It works for me and I donโ€™t have to overwrite the authenticate method.

๐Ÿ‘คRobert Mutua

0๐Ÿ‘

very simple solution without overriding backends authenticate

email_or_username = request.data.get('email_or_username', None).lower()
password = request.data.get('password', None)

auth_username = email_or_username if ('@' in email_or_username) else User.objects.get(username__iexact=email_or_username).email
user = authenticate(username=auth_username, password=password)
๐Ÿ‘คcmuna

Leave a comment