django-rest-framework-jwt icon indicating copy to clipboard operation
django-rest-framework-jwt copied to clipboard

`JSONWebTokenSerializer` always return "Unable to log in with provided credentials." when the user is not activated.

Open pddg opened this issue 6 years ago • 3 comments

My environment as follows:

  • macOS Sierra 10.12.6
  • Python 3.6.0
  • Django 2.0.5
  • djangorestframework 3.8.2
  • djangorestframework-jwt 1.11.0

What is expected instead of "Unable to log in with provided credentials."

'User account is disabled.' is expected, I think. In JSONWebTokenSerializer.validate, it seems to be implemented but not working.

How to reproduce

Install django and DRF and DRF-jwt with pip, and just start new app.

$ django-admin startproject myapp

Add rest_framework and rest_framework_jwt into INSTALLED_APPS in settings.py. Then, myapp.urls edit as follows.

from django.contrib import admin
from django.urls import path
from rest_framework_jwt.views import ObtainJSONWebToken

urlpatterns = [
    path('admin/', admin.site.urls),
    path('jwt/create',  ObtainJSONWebToken.as_view())
]

Execute migration, create superuser and login to admin page.

$ python manage.py makemigration
$ python manage.py migrate
$ python manage.py createsuperuser
$ python manage.py runserver

Then, create new user (for example, username is 'user', password is 'hogefuga') and turn off Activate from admin page. Finally, post credentials to /jwt/create.

$  curl -X POST -d "username=user&password=hogefuga" http://127.0.0.1:8000/jwt/create
{"non_field_errors":["Unable to log in with provided credentials."]}

In my opinion

In JSONWebTokenSerializer.validate(), User is authenticated using settings.AUTHENTICATION_BACKENDS ('django.contrib.auth.backends.ModelBackend' is used by default). Then, Error handling is executed based on the value which is returned by AUTHENTICATION_BACKENDS.

django.contrib.auth.backends.ModelBackend is return user instance, when

  • user which has specified username exists

and

  • the user is activated (is_active == True)

However, if the user is not activated, it returns None instead, now. So, current implementation of JSONWebTokenSerializer cannot handle 'User account is disabled.' error.

Are there any workaround for this issue? I want to distinguish these two errors.

pddg avatar May 02 '18 12:05 pddg

Problem is: AUTHENTICATION_BACKENDS = ['django.contrib.auth.backends.ModelBackend']

the default "ModelBackend" authentication backend does not allow users with is_active = False to log in.

so if you want to login with is_active= False, try AUTHENTICATION_BACKENDS = ['django.contrib.auth.backends.AllowAllUsersModelBackend']

checkout : https://docs.djangoproject.com/en/2.0/ref/contrib/auth/#django.contrib.auth.models.User.is_active

for more detail.

sbishnu019 avatar May 30 '18 09:05 sbishnu019

Sorry, I'm confused.

I don't want to allow user with is_active = False to login. I want to know the real reason of refusing login.

This issue is a problem of django-rest-framework-jwt, I think. When user which is not activated try to login, Django refuse by default('django.contrib.auth.backends.ModelBackend'). This is because of 'The user is not activated.', not 'Unable to log in with provided credentials.'.
Current implemention of django-rest-framework-jw try to handle this, but it contains a bug , so it return 'Unable to log in with provided credentials.'.

pddg avatar May 30 '18 11:05 pddg

if all(credentials.values()): user = authenticate(**credentials)

        if user:
            if not user.is_active:
                msg = _('User account is disabled.')
                raise serializers.ValidationError(msg)
               ..............

in this portion ,**authenticate(credentials) will return none when is_active = false because of 'django.contrib.auth.backends.ModelBackend'

due to user = none if user: is False so it returns 'Unable to log in with provided credentials.'. i don't know if django-rest-framework-jwt is aware of it or not ...or i may also be wrong

sbishnu019 avatar May 31 '18 10:05 sbishnu019