Skip to content
Advertisement

Getting CSRF token missing error on a django rest framework (with TokenAuthentication) public APIView

I’m having issue with Django Rest Framework and CSRF configurations. I know there are plenty of similar posts on the subject (like this one Django Rest Framework remove csrf) but most of them do not apply (I’m not using SessionAuthentication, nor Django templates), and the way DRF handles CSRF is still unclear to me.

Here is the situation :

  • I have a DRF application acting as a backend API with several routes, with Token Authentication (JWT)
  • I also have a separate frontend communicating with my API. Both are on the same domain (say https://example.com and https://example.com/backend)
  • I have a simple APIView on DRF side for registration on which I need to send POST requests. This view doesn’t require authentication.

When I send the POST request, I get a 403 Forbidden error with the following message : detail "CSRF Failed: CSRF token missing or incorrect."

Here is my view:

class RecaptchaVerifyView(APIView):
    permission_classes = []
    serializer_class = ReCaptchaSerializer

    def post(self, request, *args, **kwargs):
        serializer = self.get_serializer(data=request.data)
        if serializer.is_valid():
            return Response({'success': True}, status=status.HTTP_200_OK)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

I’ve read that DRF disables CSRF except for SessionAuthentication which I do not use. I also read that empty permission_classes should solve most of the problems. So I guess I don’t need to add csrf_exempt decorator (which I tried anyway without success btw).

The route declaration in urls.py is the following:

urlpatterns = [
    ...
    path('recaptcha_verify/', RecaptchaVerifyView.as_view(), name='recaptcha_verify'),
    ...
]

Finally, some relevant django settings:

MIDDLEWARE = [
    "corsheaders.middleware.CorsMiddleware",
    "django.middleware.security.SecurityMiddleware",
    "django.contrib.sessions.middleware.SessionMiddleware",
    "django.middleware.common.CommonMiddleware",
    "django.middleware.csrf.CsrfViewMiddleware",
    "django.contrib.auth.middleware.AuthenticationMiddleware",
    "django.contrib.messages.middleware.MessageMiddleware",
    "django.middleware.clickjacking.XFrameOptionsMiddleware",
]

...

REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'rest_framework_simplejwt.authentication.JWTAuthentication',
    )
}

Also, I’m not sure what to do with settings related to CSRF like SESSION_COOKIE_HTTPONLY, SESSION_COOKIE_SECURE, CSRF_COOKIE_SECURE, SESSION_COOKIE_NAME, CSRF_COOKIE_NAME, even with the CSRF middleware itself. Whether if I need them or not.

From what I read so far, I thought I should not bother about CSRF at all since DRF enforces the default Django behaviour, and I’m using TokenAuthentication instead of SessionAuthentication.

What am I doing wrong here ?

P.S: I also have another public view (login page) which works fine.

Advertisement

Answer

The key line missing in the setup was authentication_classes = () in addition to the empty permission_classes list.

So my APIView class now looks like this :

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

    @swagger_auto_schema(responses={status.HTTP_200_OK: openapi.Response("")})
    def post(self, request, *args, **kwargs):
        serializer = ReCaptchaSerializer(data=request.data)
        if serializer.is_valid():
            return Response({'success': True}, status=status.HTTP_200_OK)
        else:
            return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

And same for the registration view. Spent hours on this one, but this makes sense now with DRF documentation stating that without explicit declaration of authentication_classes, default SessionAuthentication is enforced thus requires CSRF token.

User contributions licensed under: CC BY-SA
10 People found this is helpful
Advertisement