9

I am trying to get this working but don't know if this is possible. It should be doing it like this.

I developed a web app using Django + Rest-Framework + jQuery, and I want to have an external application to consume the same REST API, using JWT Tokens for authentication.

My current config is like this.

settings.py

REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'rest_framework.authentication.SessionAuthentication',
        'rest_framework_simplejwt.authentication.JWTAuthentication',
    ],
    'DEFAULT_RENDERER_CLASS': [
        'rest_framework.renderers.JSONRenderer',
    ]
}

SIMPLE_JWT = {
    'AUTH_HEADER_TYPES': ('Bearer',),
    }

views.py

class ListFileView(APIView):
    permission_classes = (IsAuthenticated,)

    def get(self, request, *args, **kwargs):
        user = request.user

        if user:

            obj = Foo.objects.filter(owner=user)
            serializer = FooSerializer(obj, many=True)
            data = serializer.data

            return Response(data, status=status.HTTP_200_OK)

        return Response({'detail': 'You have no access to files.'}, status=status.HTTP_400_BAD_REQUEST)

The tricky part is that when I use:

permission_classes = (IsAuthenticated,)

I can perform ajax calls from an external application (using a valid JWT token), but jQuery calls from within the app (with an authenticated user) fail with:

{"detail":"Authentication credentials were not provided."}

And on the other hand, if I use autentication_classes instead of permission_classes:

authentication_classes = (SessionAuthentication, BasicAuthentication)

I can perform ajax calls from within the web app using jQuery, but external calls fail with the same 403 error.

I tried using both like this:

class ListFileView(APIView):
    authentication_classes = (SessionAuthentication, BasicAuthentication)
    permission_classes = (IsAuthenticated,)

    def get(self, request, *args, **kwargs):
        ...

but the external calls are also rejected.

Is it possible to have these two types of Auth working together in the same class view, or should I separate into two endpoints?

EDIT

Example calls from within the app with jQuery:

<script type="text/javascript">

function loadTblDocs() {
  $("#tbl-docs > tbody").empty();

  $.ajaxSetup({
      headers: { "X-CSRFToken": '{{csrf_token}}' }
    });

  $.ajax({
    type: 'GET',
    contentType: "application/json; charset=utf-8",
    url: "/api/list/",
    dataType: "json",
    success: function (data) {
                console.log(data);
                }
    });
};

</script>

And externally via VBA code:

Set web = CreateObject("WinHttp.WinHttpRequest.5.1")
web.Open "GET", "/api/list/", False
web.SetRequestHeader "Authorization", "Bearer <JWT_TOKEN_HERE>"
web.Send
7
  • can you show how you are calling the API ? Commented Apr 6, 2019 at 14:32
  • @JPG with jQuery or externally? Commented Apr 6, 2019 at 14:34
  • @JPG added both jQuery and VBA example calls. Commented Apr 6, 2019 at 14:40
  • can you post your login view as well? are you using Django's login method there? Commented Apr 6, 2019 at 15:23
  • Yes, I am using the standard django login Commented Apr 6, 2019 at 15:42

1 Answer 1

13

I couldn't work out exactly what is going on in your case, because the behavior in the 3 cases you shared does not seem to be consistent, but going to explain simply how authentication and authorization is determined in DRF, this might help you figure the issue out.

You should be able to use two authentication classes with no problems. With DRF, authentication works like this:

At each request, DRF goes over the provided authentication classes, in the order they are defined. For each class, there are 3 cases:

  1. If it can authenticate the request with the current class, DRF sets request.user. From this point on, this request is authenticated.
  2. If no authentication credentials are present, DRF skips that class
  3. If authentication credentials are present but invalid, such as an invalid JWT token in Authorization header, DRF raises an exception and returns a 403 response.

DRF views normally use the DEFAULT_AUTHENTICATION_CLASSES variable defined in the settings file, but if you provide them in a view, settings are overridden.

Authorization comes into play when you add permission_classes to your views. permission_classes control access to your views, so when you add IsAuthenticated to a view's permission classes, DRF rejects the request with a 403 response if you try to access that view without an authenticated user.

So in your initial case, your internal AJAX request failing in this case suggests that there is no authenticated user data in your request session. I do not know what could be causing this. Perhaps you do not update request session in your login view (Django's login method should do this automatically).

In your second case, you remove permission_classes from the view, so the view will serve the request regardless if there is an authenticated user in the request or not. So it is expected that your internal AJAX request succeeds here, but I do not know why your external request fails in this case.

In your third case, from the point of your internal AJAX request, the scenario seems to be the same as the first case, so I do not know why your internal AJAX request succeeds in this case but not in the first case. The external request failing here is expected because you added the IsAuthenticated permission class to the view, but did not include JWTAuthentication in the authentication_classes, so your request can not be authenticated with your JWT token here.

Sign up to request clarification or add additional context in comments.

1 Comment

You are right and thank you for this detailed explanation. It turns out I was actually using a wrong settings.py file in production, which was missing this line rest_framework.authentication.SessionAuthentication in DRF DEFAULT_AUTHENTICATION_CLASSES, it is now working fine.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.