Search code examples
djangodjango-rest-frameworkjwtdjango-rest-framework-simplejwt

Django Rest Framework + SimpleJWT returns 401 on unprotected routes


I have configured DRF to use JWT as an authentication scheme and for the most part works correctly however when a user's token & refresh token are no longer valid rather than returning a 200 as an unauthorized user for unprotected routes and displaying the website as if they are no longer logged in the backend returns a 401. I am new to the Django auth scheme / middleware setup but my assumption would be that if the default is AllowAny then a bad token would be ignored. Is there a configuration that I am missing.

From the DRF Docs

If not specified, this setting defaults to allowing unrestricted access:

'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.AllowAny', ]

my settings.py

REST_FRAMEWORK = {
    ...
    "DEFAULT_AUTHENTICATION_CLASSES": (
        "rest_framework.authentication.BasicAuthentication",
        "rest_framework_simplejwt.authentication.JWTAuthentication",
    ),
}

SIMPLE_JWT = {
    "ACCESS_TOKEN_LIFETIME": datetime.timedelta(minutes=15),
    "REFRESH_TOKEN_LIFETIME": datetime.timedelta(days=2),
    ...
}

Example ViewSet that returns 401 with bad access token

class PromoApiSet(ViewSet):
    serializer_class = PromoSerializer

    def get_queryset(self, *args, **kwargs):
        time_now = timezone.now()
        return PromoNotification.objects.filter(
            end_date__gte=time_now, start_date__lte=time_now
        )

    # @method_decorator(cache_page(120))
    def list(self, request):
        promos = self.get_queryset()

        serializer = self.serializer_class(promos, many=True)
        promos_data = serializer.data

        response_data = {"promos": promos_data}

        return Response(response_data)

Solution

  • Invalid, malformed, or otherwise incorrect credentials will raise a 401, see the source code for the JWTAuthentication class.

    This makes sense to me, but if you want the behavior to be "silently ignore this header if there was an error" then you need to override the method and, well, ignore the errors:

    class MyJWTAuth(JWTAuthentication):
        def authenticate(self, request):
            try:
                return super().authenticate(self, request)
            except (InvalidToken, AuthenticationFailed):
                return None
    
    # in your settings, use your new class
    "DEFAULT_AUTHENTICATION_CLASSES": (
            "rest_framework.authentication.BasicAuthentication",
            "my.project.module.MyJWTAuth",
    ),
    

    This will work, but I wouldn't recommend it in general. If you want more granular "ignoring" of specific errors then you'll need to override more functionality, or vendor the class and make modifications directly.