django.contrib.auth.login() function not returning any user as logged in

Viewed 874

I have created a basic app using Django's built in authentication system. I successfully created a User object in the shell using >>python manage.py createsuperuser.

I then created a basic view, 'UserLogin' along with corresponding serializers/urls, to log an existing user in using the django.contrib.auth authenticate(), and login() functions. Upon testing with the credentials of my created user, the login function seemed to have worked successfully.

To test this, I created another view function, 'CurrentUser' which returns the username of the currently logged in user. However, this view returns the user as empty.

Why would the 'CurrentUser' view be returning no user as logged in? I have attached my code (minus imports) below.

views.py:

class UserLogin(APIView):
    def post(self, request, format = None):
        serializer = UserLoginSerializer(data=request.data)
        if serializer.is_valid():
            user = authenticate(username=serializer.validated_data["username"], password=serializer.validated_data["password"])
            if user is not None:
                login(request, user)
                return Response(UserSerializer(user).data, status=status.HTTP_201_CREATED)
            return Response("Invalid username/password", status=status.HTTP_401_UNAUTHORIZED)
        return Response(serializer.errors, status=status.HTTP_401_UNAUTHORIZED)

class CurrentUser(APIView):
    def get(self, request, format = None):
        return Response(self.request.user.username)

serializers.py:

class UserSerializer(serializers.ModelSerializer):
    class Meta:
        model = User
        fields = ['id', 'username']

class UserLoginSerializer(serializers.Serializer):
    username = serializers.CharField(max_length=300, required=True)
    password = serializers.CharField(required=True, write_only=True)

urls.py:

urlpatterns = [
    path('login/', views.UserLogin.as_view()),
    path('current/', views.CurrentUser.as_view())
]

Any guidance would be much appreciated.

Thanks

6 Answers

You have to set the default auth class as session authenticate class in DRF settings. Read more about it here [1].

Session auth uses session id to identify the user. So you have to send the cookie based session id in the request. Read about session auth here [2].

for example:

REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'rest_framework.authentication.SessionAuthentication', # <-- set this class
    ]
}

Use this code:

def post(self, request, format = None):
    serializer = UserLoginSerializer(data=request.data)
    if serializer.is_valid():
        user = authenticate(username=serializer.validated_data["username"], password=serializer.validated_data["password"])
        if user:
            return Response(UserSerializer(user).data, status=status.HTTP_201_CREATED)
        return Response("Invalid username/password", status=status.HTTP_401_UNAUTHORIZED)
    return Response(serializer.errors, status=status.HTTP_401_UNAUTHORIZED)

But my recommendation is to use Token auth [3].

To use token auth 2 things will change:

  1. The default auth class in DRF settings
  2. When sending a request to any DRF API view you to send the Auth header as Token <token-value>

Your post method and API views code will remain same.

[1] https://www.django-rest-framework.org/api-guide/authentication/#setting-the-authentication-scheme

[2] https://www.django-rest-framework.org/api-guide/authentication/#sessionauthentication

[3] https://www.django-rest-framework.org/api-guide/authentication/#tokenauthentication

I am using DRF for login. You can check this serializers and login view.

serializers.py:

    class LoginSerializer(serializers.Serializer):
              username = serializers.CharField()
              password = serializers.CharField()

    def validate(self, data):
         user = authenticate(**data)
         if user and user.is_active:
               return user
    raise serializers.ValidationError("Incorrect Credentials")

login view:

    class LoginAPIView(generics.GenericAPIView):
          queryset = User.objects.all()
          permission_classes = [AllowAny]
          serializer_class = LoginSerializer

 def post(self, request, *args, **kwargs):
     serializer = self.get_serializer(data=request.data)
     serializer.is_valid(raise_exception=True)
     user = serializer.validated_data
     return Response({
        "user": UserSerializer(user, context=self.get_serializer_context()).data,
        "token": AuthToken.objects.create(user)[1]
    }) 

settings.py rest framework classes :

REST_FRAMEWORK = {

    'DEFAULT_AUTHENTICATION_CLASSES': [
    # 'rest_framework.authentication.BasicAuthentication',
    # 'rest_framework.authentication.SessionAuthentication',
    'knox.auth.TokenAuthentication',
],
    
'DEFAULT_PERMISSION_CLASSES': [
    'rest_framework.permissions.DjangoModelPermissionsOrAnonReadOnly'
],

'DEFAULT_FILTER_BACKENDS': ['django_filters.rest_framework.DjangoFilterBackend']

}

You were trying to test the authentication in the wrong way. This is API, which is stateless. That is Django doesn't have any idea about the previous request that you have made to the UserLogin.

So, you need to attach the token and session-id (or something that can be used to identify the user) with the "second request".

Since you are using DRF, you don't have to write your own authentication mechanisms. You can use DRF's BasicAuthentication (or any other authentication scheme) to authenticate.

So, change your view as

from rest_framework.authentication import BasicAuthentication
from rest_framework.permissions import IsAuthenticated


class CurrentUser(APIView):
    authentication_classes = (BasicAuthentication,)
    permission_classes = (IsAuthenticated,)

    def get(self, request, format=None):
        return Response({
            "user": {
                "id": request.user.id,
                "username": request.user.username
            }})

Execution using requests

In [5]: import requests                                                                                                                                                                                            

In [6]: from requests import auth                                                                                                                                                                                  

In [7]: response = requests.get(url=url, auth=auth.HTTPBasicAuth("admin", "passme123@#$"))                                                                                                                         

In [8]: print(response.json())                                                                                                                                                                                     
{'user': {'id': 1, 'username': 'admin'}}

Since you are using DRF, you can make use of any of these DRF authentications schemes in your code-base

Since you are using Django rest framework, i m assuming you are developing rest apis. Rest Apis by design are meant to be stateless meaning no two requests will be dependent on each other.

What this essentially means is you have to authenticate every request and set the user in request after authentication.

There are many ways of doing it like many have suggested to use basicAuth, or token based authentication. All of which can we easily implemented by reading documentation Auth Doc

The out of the box django views uses cookies to identify logged in user. Which in this case is not helpful.

A cause for this could be a missing SessionMiddleware:

  1. Your /login view works
  2. It persists stuff in the session but the middleware isn't installed
  3. The next request to /current isn't bound to the authenticated session

Can you double check that settings.MIDDLEWARES contain django.contrib.sessions.middleware.SessionMiddleware?

Related