Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Django Rest Framework - Using Session and Token Auth

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
like image 335
drec4s Avatar asked Apr 06 '19 14:04

drec4s


People also ask

What is the best authentication for Django REST framework?

And these are all provided by drf(django rest framework) and other than these like oauth, oauth2 based authentication are provided by the efforts of the community with help of other python packages. And they can be easily used in the production environment.

How does session authentication work in Django?

After logging in, the server validates the credentials. If valid, it generates a session, stores it, and then sends the session ID back to the browser. The browser stores the session ID as a cookie, which gets sent anytime a request is made to the server. Session-based auth is stateful.

What is token authentication in Django REST framework?

Token authentication refers to exchanging username and password for a token that will be used in all subsequent requests so to identify the user on the server side. This article revolves about implementing token authentication using Django REST Framework to make an API.


1 Answers

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.

like image 82
Ozgur Akcali Avatar answered Oct 04 '22 00:10

Ozgur Akcali