1

I have a class that behaves differently depending on if the user is authenticated or not:

class SomeClass(APIView):
    authentication_classes = ()
    permission_classes = ()

    def get(self, request):
        if request.user.is_authenticated:
            # do something...
        else:
            # do something else...

it used to work perfectly with Django 3.2.5 and JSONWebTokenAuthentication. however, I had to upgrade to Django 4.x and TokenAuthentication... with:

authentication_classes = (TokenAuthentication, )

the user is available but the request returns 401 to anonymous users... with:

authentication_classes = ()

anonymous requests are accepted, but I can't see the data of authenticated users.

csgeek
  • 711
  • 6
  • 15
mr.louis
  • 21
  • 4
  • How did you get the Token? First, you have to be logged in and then pass the token in the header – MecaTheclau Aug 30 '22 at 09:39
  • yep. the token is being passed in the header (Authorization: Token xxxx) - and it works as expected when with `authentication_classes = (TokenAuthentication, )`. the very same user is not being recognised in request.user when "authentication_classes = ()". – mr.louis Aug 31 '22 at 10:15
  • User permission class in your view e.g ```permission_classes = [IsAuthenticated]``` – Shedrack Dec 18 '22 at 14:53

2 Answers2

0

You need to log in and save the user in the session

from rest_framework import status
from rest_framework.views import APIView
from rest_framework.response import Response
from django.contrib.auth import authenticate, login

class LoginView(APIView):
    # authentication_classes = ()
    permission_classes = ()
    def post(self, request):
        email = request.data.get("email")
        password = request.data.get("password")
        user = authenticate(email=email, password=password) # TODO Check if use can be authenticated
        
        if user:
            login(request, user) #TODO use login to store session of the logged in user
            return Response({
                "userId":user.id,
                "firstname": user.first_name,
                "lastname": user.last_name,
                "email": user.email,
                "token": user.auth_token.key,
                "other_fiels": "other"
            })
        else:
            return Response({"error": (email, password)}, status=status.HTTP_400_BAD_REQUEST)
MecaTheclau
  • 188
  • 1
  • 3
  • 14
  • my user is logged in and the token is being passed with the request. the user information is however only visible within the function if the class checks the authentication with `authentication_classes = (TokenAuthentication, )` which makes the function only executed by authenticated users. if the class does not check the authentication with `authentication_classes = ()` authenticated users appear anonymous in `request.user`. it used to work with djangorestframework-jwt which is no longer supported in django 4. my current work around is two urls (authenticated & anonymous) and two classes. – mr.louis Aug 31 '22 at 11:00
0

ok. i finally ended up extending the TokenAuthentication class:

from rest_framework.authentication import TokenAuthentication

class MyTokenAuthentication(TokenAuthentication):

    def authenticate_credentials(self, key):
        model = self.get_model()
        try:
            token = model.objects.select_related('user').get(key=key)
        except model.DoesNotExist:
            # raise exceptions.AuthenticationFailed(_('Invalid token.'))
            return None

        if not token.user.is_active:
            # raise exceptions.AuthenticationFailed(_('User inactive or deleted.'))
            return None
        return token.user, token

instead of raising an exception i return None. this way the request object within a function contains the user data if the user is authenticated otherwise anonymous.

UPDATE:

after a long fight and great help from @abdul-aziz-barkat we finally find the bug. it was not the authentication_classes() that caused the error, but the permission_classes() with the IsAuthenticated value set to default in the settings.py

this is how it works with TokenAuthentication:

class GroupProfileView(APIView):
    authentication_classes = (TokenAuthentication, )
    permission_classes = ()
mr.louis
  • 21
  • 4
  • Huh, why would you need to do this? This would mean you are sending the `Authorization` header even though the user is not authenticated, obviously getting an error in that case should be intended. You need to fix your code on the client side instead of doing this. – Abdul Aziz Barkat Aug 31 '22 at 13:34
  • my view generates a list of articles with comments, ratings and other related stuff from the database. these non critical articles can be read by authenticated and anonymous users. if the user is however authenticated, additional data (like i.e. their own ratings) is rendered as well. in my opinion this is a better solution then calling two different urls from the client and having two views that do almost the very same task... this hack is NOT a replacement for views requiring the TokenAuthentication. its a replacement for anonymous calls. – mr.louis Aug 31 '22 at 17:06
  • naming the class `MyTokenAuthentication` may be misleading - the name should be something more like `AnonymousOrAuthorized` ;-) – mr.louis Aug 31 '22 at 17:15
  • No, I am not saying you need to implement 2 views. `TokenAuthentication` works properly with anonymous users. The problem here is that from the client you are sending an invalid "Authorization" header, you simply **don't** need to send that header if you are not logged in (The url can stay the same). Your hack as a "replacement for anonymous calls" is not required, Token authentication works with anonymous calls even without doing this. You just need to fix the code at the client side. – Abdul Aziz Barkat Sep 01 '22 at 04:37
  • my client is not sending any Authorization headers if the user is not logged in. therefore the `TokenAuthentication` class behaves correctly raising an exception, returning a 401 error and not calling the `get` function at all. this is not what i am aiming for - what i want is the view to be available for anonymous users, BUT just in case the user is logged in - i want to be able to access his request.user object within the function in order to personalize his response. with `authentication_classes = ()` the request.user object of correctly **authenticated** users returns `AnonymousUser`. – mr.louis Sep 01 '22 at 08:21
  • On the contrary your client _is_ sending an "Authorization" header. The `authenticate_credentials` method is only called if the header is present otherwise the `authenticate` method of the class returns `None` on it's own. If you test this out with Postman or some other tool you'll see that even without this class your code would have worked (As long as you didn't send the header). – Abdul Aziz Barkat Sep 01 '22 at 09:35
  • there is no Authorization header in my anonymous client requests. the Authorization header with the token is only present if the user is logged in. i doubt, you understand what i am looking for... i don't want anonymous requests to be rejected with HTTP_401 - i want them to receive the news feed. but if the request comes from an authenticated user, i want be able to access his `request.user` object in order to personalize his response. the `request.user` data is only present with my hack in the `def get(self, request)` function. – mr.louis Sep 01 '22 at 11:27
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/247733/discussion-between-abdul-aziz-barkat-and-mr-louis). – Abdul Aziz Barkat Sep 01 '22 at 12:17