8

I am using rest_framework_simplejwt to authenticate my users but, in some views i need to ignore it, because these are public views. i want to check the token into view flow. The expected behaviour is:

In public view

  • Avoid token validation: If is there an expired or invalid token, ignore it and let me in to validate it into APIView

Actually rest_framework_simplejwt checks token and raise 401 if token is invalid or expired...

I tried disabling authentication_classes within APIView like this:

class SpecificProductApi(APIView):

    def get_authenticators(self):
        if self.request.method == 'GET':
            self.authentication_classes = []
        return super(SpecificProductApi, self).get_authenticators()

but if i disable it before enter GET APIView method, i can't do if reques.user.is_authenticated: because I disabled the token :(

Exists a way to enable entering to api http method and check users manually into view? thanks

Jhon Sanz
  • 107
  • 1
  • 6
  • I don't understand what problems are you facing when you do **`request.user.is_authenticated`**. Can you explain issue? – JPG Dec 17 '19 at 14:41
  • Hello! thanks for answered, happens that if I use `self.authentication_classes = []`, later inside the view using `request.user.is_authenticated` has no effect, it always return false, and I need handle authentication manually inside the view avoiding validation outside it, i hope have been more clear, thank you a lot :) – Jhon Sanz Dec 19 '19 at 22:56
  • I would like to know what is the standard way to do it. like a specification to handle this. someone knows? – Darwin Mar 01 '21 at 20:42

4 Answers4

9

I get it done by adding authentication_classes = []

from rest_framework import permissions

class SpecificProductApi(APIView):
    permission_classes = [permissions.AllowAny]
    authentication_classes = []
Fran
  • 103
  • 2
  • 8
9

You could simply use authentication_classes = [] in the view, but this always bypasses the JWT authentication, even when a valid Authorization-header with the token is present. You'd better extend the JWTAuthentication-class as follows (similar to the comment of Jhon Edwin Sanz Gonzalez):

from rest_framework_simplejwt.authentication import JWTAuthentication
from rest_framework_simplejwt.exceptions import InvalidToken


class JWTAuthenticationSafe(JWTAuthentication):
    def authenticate(self, request):
        try:
            return super().authenticate(request=request)
        except InvalidToken:
            return None

Then use authentication_classes = [JWTAuthenticationSafe] in your view.

Neman
  • 1,237
  • 2
  • 13
  • 16
1

Have a very similar problem. To create public endpoints you are forced to override the authenticators, or else you will return 401/403 on expired/missing token.

However, a public endpoint does not mean that it should not have authentication. Rather it should have one response for no-auth / expired-auth and another for valid auth.

I don't know if this is the "correct" way, but this is what I came up with facing the same problem.

Override the authenticators as you have done, and add an additional method to validate the authenticators in your view.

For example:

class SomeApiView(APIView):
    def get_authenticators(self):
        # Override standard view authenticators.
        # Public endpoint, no auth is enforced.
        return []

    def get_auth(self):
        # Return a generator of all authenticators.
        return (auth() for auth in self.authentication_classes)

    def get_auth_detail(self, request):
        # Evaluate each authenticator and return the first valid authentication.
        for auth in self.get_auth():
            # You probably need try / except here to catch authenticators 
            # that are invalid (403/401) in the case of multiple authentication 
            # classes--such as token auth, session auth, etc...
            auth_detail = auth.authenticate(request)
            if auth_detail:
                return auth_detail

        return None, None

    def post(self, request):
        # Returns a tuple of (User, JWT), can be (None, None)
        user, jwt = self.get_auth_detail(request)  

        # Do your magic based on user value.
        if user:
            # User is authenticated.
        else:
            # User is anon / not-authenticated.
James
  • 2,488
  • 2
  • 28
  • 45
  • 1
    Hello, thank you a lot, this is awesome. I just changed `get_auth_detail` method to `try: auth_detail = auth.authenticate(request) except: auth_detail = None`, I don't know why but `auth_detail = auth.authenticate(request)` raised Http403 exception and I used try catch. I will be searching how to improve that solution. Thank you a lot – Jhon Sanz Jan 02 '20 at 22:27
  • If you have multiple authentication classes defined, like session auth, token auth, then most likely one authenticator will pass where the other authenticator will fail 403. This will depend on ordering. So possibly you have to evaluate all authenticators until they all fail or a single valid authentication. ie, try / except. I think I'm only using a single jwt authenticator in my project so I don't see this issue. – James Jan 02 '20 at 22:32
0

You just need to specify permission class for the relevant view

from rest_framework.permissions import AllowAny

class SpecificProductApi(APIView):
    permission_classes = (AllowAny, )

This permission allows any person to hit this specific view through a URL.

d3corator
  • 1,154
  • 1
  • 9
  • 23
  • In my testing this does not work. An expired token triggers a 401 response; hence my answer. – James Jan 01 '20 at 17:42