jpadilla / django-rest-framework-jwt

JSON Web Token Authentication support for Django REST Framework
http://jpadilla.github.io/django-rest-framework-jwt/
MIT License
3.19k stars 650 forks source link

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

Open pddg opened 6 years ago

pddg commented 6 years ago

My environment as follows:

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

and

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.

sbishnu019 commented 6 years ago

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.

pddg commented 6 years ago

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

sbishnu019 commented 6 years ago

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