Django rest framework JWT and custom authentication backend
Asked Answered
M

2

6

I have a custom user model and have created a custom authentication backend. I am using django rest framework, and django rest framework JWT for token authentication.

User model:

class User(AbstractBaseUser, PermissionsMixin):
    email = models.EmailField(
        unique=True,
        max_length=254,
    )
    first_name = models.CharField(max_length=15)
    last_name = models.CharField(max_length=15)
    mobile = models.IntegerField(unique=True)
    date_joined = models.DateTimeField(default=timezone.now)
    is_active = models.BooleanField(default=True)
    is_admin = models.BooleanField(default=False)
    objects = UserManager()
    USERNAME_FIELD = 'email'
    REQUIRED_FIELDS = ['first_name', 'last_name', 'mobile']

Auth backend:

class EmailOrMobileAuthBackend(object):
    def authenticate(self, username=None, password=None):
        try:
            user = get_user_model().objects.get(email=username)
            if user.check_password(password):
                return user
        except User.DoesNotExist:
            if username.isdigit():
                try:
                    user = get_user_model().objects.get(mobile=username)
                    if user.check_password(password):
                        return user
                except User.DoesNotExist:
                    return None
            else:
                return None

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

And have added in the settings.py:

AUTHENTICATION_BACKENDS = ('accounts.email_mobile_auth_backend.EmailOrMobileAuthBackend',)

While to log in to django admin site, both the email and mobile number works fine in authenticating the user. However, when I try get the token for the user using django rest framework JWT, I get an error:

curl -X POST -d "[email protected]&password=123123" http://localhost/api-token-auth/

"non_field_errors": [
    "Unable to log in with provided credentials."
  ]

I have also added the custom auth backend class in the Default authentication for Rest framework, but its still not working:

REST_FRAMEWORK = {
    ...
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'accounts.email_mobile_auth_backend.EmailOrMobileAuthBackend',
        'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
    ),
}

What am I missing? Why is it that its working while loggin to the django admin site, but get error when getting token with django rest framework jwt?

update

I have made another auth backend as advised and added it to the DEFAULT_AUTHENTICATION_CLASSES, but even this is not working.

class DrfAuthBackend(BaseAuthentication):
    def authenticate(self, username=None, password=None):
        try:
            user = get_user_model().objects.get(email=username)
            if user.check_password(password):
                return user, None
        except User.DoesNotExist:
            if username.isdigit():
                try:
                    user = get_user_model().objects.get(mobile=username)
                    if user.check_password(password):
                        return user, None
                except User.DoesNotExist:
                    return None
            else:
                return None

Settings:

REST_FRAMEWORK = {
    'DEFAULT_PERMISSION_CLASSES': (
        'rest_framework.permissions.IsAdminUser',
    ),
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'accounts.email_mobile_auth_backend.EmailOrMobileAuthBackend',
        'accounts.email_mobile_auth_backend.DrfAuthBackend',
        'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
    ),
}

update

Changing the args in the auth class from username to email seems to work for obtaining auth_token but again its not working to log in to admin site.

class EmailOrMobileAuthBackend(object):
    def authenticate(self, email=None, password=None):
        try:
            user = get_user_model().objects.get(email=email)
            if user.check_password(password):
                return user
        except User.DoesNotExist:
            if email.isdigit():
                try:
                    user = get_user_model().objects.get(mobile=email)
                    if user.check_password(password):
                        return user
                except User.DoesNotExist:
                    return None
            else:
                return None

    def get_user(self, user_id):
        try:
            return get_user_model().objects.get(pk=user_id)
        except User.DoesNotExist:
            return None 
Millenarian answered 26/5, 2017 at 20:30 Comment(4)
Were you able to finally solve the issue? Got a similar issue where am using django-rest-framework with django-rest-framework-jwt and I need to allow a user to get a JWT token by logging in via either username or email or mobile_numberAndros
@Andros I am using two authentication backend. One for the django admin and the other for the rest api.Millenarian
Are the two backends allowing a user to login via email or mobile? If yes, do you mind sharing how you went about it especially for the rest API? Thanks.Andros
@Andros Yes, sure. I created two authentication backend (both are basically the same). You can find them here and there. And then in the settings I have added these along the lines.Millenarian
R
2

You should check the DRF documentation for custom authentication backends.

I think your custom authentication backend is breaking it, you may solve it by removing yours from DRF settings:

REST_FRAMEWORK = {
    ...
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
    ),
}

Or by fixing yours which is basically not the same as for Django custom authentication, as you should extend from authentication.BaseAuthentication and it returns a tuple.

from rest_framework import authentication


class DrfAuthBackend(authentication.BaseAuthentication):
    def authenticate(self, email=None, password=None):
        try:
            user = get_user_model().objects.get(email=email)
            if user.check_password(password):
                return user, None
        except User.DoesNotExist:
            if email.isdigit():
                try:
                    user = get_user_model().objects.get(mobile=email)
                    if user.check_password(password):
                        return user, None
                except User.DoesNotExist:
                    return None
            else:
                return None

Then use that in the DRF settings:

REST_FRAMEWORK = {
    'DEFAULT_PERMISSION_CLASSES': (
        'rest_framework.permissions.IsAdminUser',
    ),
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'accounts.email_mobile_auth_backend.DrfAuthBackend',
        'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
    ),
}

For the Django login:

class EmailOrMobileAuthBackend(object):
    def authenticate(self, username=None, password=None):
        try:
            user = get_user_model().objects.get(email=username)
            if user.check_password(password):
                return user
        except User.DoesNotExist:
            if username.isdigit():
                try:
                    user = get_user_model().objects.get(mobile=username)
                    if user.check_password(password):
                        return user
                except User.DoesNotExist:
                    return None
            else:
                return None

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

Then for the settings:

AUTHENTICATION_BACKENDS = ('accounts.email_mobile_auth_backend.EmailOrMobileAuthBackend',)
Rockwood answered 27/5, 2017 at 0:41 Comment(2)
I have tried your advice and updated the question, but its still not working. However, changing the args in the authentication() method from username to email does the work without the need to add it to the DEFAULT_AUTHENTICATION_CLASSES. But again its not working to log in to admin site.Millenarian
The Django custom authentication does not support email as keyword argument, you should only use it for Django and write a different one for DRFRockwood
C
0

In your settings.py file, add the following:

AUTH_USER_MODEL = 'accounts.email_mobile_auth_backend.EmailOrMobileAuthBackend'

Cariotta answered 6/1, 2021 at 4:14 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.