Multiple USERNAME_FIELD in django user model
Asked Answered
B

4

19

My custom user model:

class MyUser(AbstractBaseUser):
    username = models.CharField(unique=True,max_length=30)
    email = models.EmailField(unique=True,max_length=75)
    is_staff = models.IntegerField(default=False)
    is_active = models.IntegerField(default=False)
    date_joined = models.DateTimeField(default=None)

    # Use default usermanager
    objects = UserManager()

    USERNAME_FIELD = 'email'

Is there a way to specify multiple USERNAME_FIELD ? Something like ['email','username'] so that users can login via email as well as username ?

Baumgardner answered 12/7, 2015 at 16:39 Comment(0)
R
19

The USERNAME_FIELD setting does not support a list. You could create a custom authentication backend that tries to look up the user on the 'email' or 'username' fields.

from django.db.models import Q

from django.contrib.auth import get_user_model

MyUser = get_user_model()

class UsernameOrEmailBackend(object):
    def authenticate(self, username=None, password=None, **kwargs):
        try:
           # Try to fetch the user by searching the username or email field
            user = MyUser.objects.get(Q(username=username)|Q(email=username))
            if user.check_password(password):
                return user
        except MyUser.DoesNotExist:
            # Run the default password hasher once to reduce the timing
            # difference between an existing and a non-existing user (#20760).
            MyUser().set_password(password)

Then, in your settings.py set AUTHENTICATION_BACKENDS to your authentication backend:

 AUTHENTICATION_BACKENDS = ('path.to.UsernameOrEmailBackend,)\

Note that this solution isn't perfect. For example, password resets would only work with the field specified in your USERNAME_FIELD setting.

Razzledazzle answered 12/7, 2015 at 17:32 Comment(1)
BEWARE that in the default django implementation, the email field is not unique...security, weirdness, etc AHOI.Breunig
M
12

We can do that by implementing our own Email authentication backend.

You can do something like below:

Step-1 Substite the custom User model in settings:

Since we would not be using Django's default User model for authentication, we need to define our custom MyUser model in settings.py. Specify MyUser as the AUTH_USER_MODEL in the project's settings.

AUTH_USER_MODEL = 'myapp.MyUser'

Step-2 Write the logic for the custom authentication backend:

To write our own authentication backend, we need to implement atleast two methods i.e. get_user(user_id) and authenticate(**credentials).

from django.contrib.auth import get_user_model
from django.contrib.auth.models import check_password

class MyEmailBackend(object):
    """
    Custom Email Backend to perform authentication via email
    """
    def authenticate(self, username=None, password=None):
        my_user_model = get_user_model()
        try:
            user = my_user_model.objects.get(email=username)
            if user.check_password(password):
                return user # return user on valid credentials
        except my_user_model.DoesNotExist:
            return None # return None if custom user model does not exist 
        except:
            return None # return None in case of other exceptions

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

Step-3 Specify the custom authentication backend in settings:

After writing the custom authentication backend, specify this authentication backend in the AUTHENTICATION_BACKENDS setting.

AUTHENTICATION_BACKENDS contains the list of authentication backends to be used. Django tries authenticating across all of its authentication backends. If the first authentication method fails, Django tries the second one, and so on, until all backends have been attempted.

AUTHENTICATION_BACKENDS = (
    'my_app.backends.MyEmailBackend', # our custom authentication backend
    'django.contrib.auth.backends.ModelBackend' # fallback to default authentication backend if first fails 
    )

If authentication via MyEmailBackend fails i.e user could not be authenticated via email, then we use the Django's default authentication ModelBackend which will try to authenticate via username field of MyUser model.

Monocle answered 12/7, 2015 at 17:34 Comment(2)
I created similar custom authentication backend now I want to pass email and phone number fields also while log in to this method. But I get only username and password field kwargs is blank Any Idea how to get all fields?Brigitta
You don't need the from django.contrib.auth.models import check_password it is already a property of the auth_user_model object. Otherwise nice solution.Gassy
S
2

No, you cannot have more than one field defined in USERNAME_FIELD.

One option would be to write your own custom login to check for both fields yourself. https://docs.djangoproject.com/en/1.8/topics/auth/customizing/

i.e. change the backend to your own. AUTHENTICATION_BACKENDS then write an authenticate method and check the username on both fields in the DB.

PS you may want to use unique_together on your model so you don't run into problems.

Another option would be to use the actual field username to store both string and email.

Spin answered 12/7, 2015 at 17:33 Comment(0)
X
0

Unfortunately, not out-of-the box.

The auth contrib module asserts that the USERNAME_FIELD value is mono-valued.

See https://github.com/django/django/search?q=USERNAME_FIELD

If you want to have a multi-valued USERNAME_FIELD, you will either have to write the corresponding logic or to find a package that allow it.

Xochitlxp answered 12/7, 2015 at 17:32 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.