403 Forbidden: "CSRF Failed: CSRF token missing." on DRF api-token-auth/ after applying csrf_exempt

I'm encountering a persistent 403 Forbidden error with the detail:

CSRF Failed: CSRF token missing.

This happens when trying to obtain an authentication token using Django REST Framework's built-in api-token-auth/ endpoint.


Context

  • I am sending a POST request from Postman (using raw and application/json for the body).

  • The CSRF protection is interfering because Postman, as an external client, doesn't handle session cookies or CSRF tokens.

  • I attempted to fix this by explicitly applying the @csrf_exempt decorator to the view in my urls.py, but the error remains.


Configuration and Code

Here are the relevant snippets from my project setup:

1. settings.py (Middleware and DRF Authentication)

My middleware includes CSRF protection, and I have SessionAuthentication enabled, which seems to be causing the conflict.

MIDDLEWARE = [
    "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_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
    'PAGE_SIZE': 10,
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'rest_framework.authentication.TokenAuthentication',
        'rest_framework.authentication.SessionAuthentication',
    ),
    'DEFAULT_PERMISSION_CLASSES': (
        'rest_framework.permissions.IsAuthenticated',
    ),
}

2. urls.py (Where csrf_exempt is applied)

This is how I'm currently trying to exempt the view. I have imported csrf_exempt from django.views.decorators.csrf.

from django.contrib import admin
from django.urls import path,include
from rest_framework.authtoken import views
from django.views.decorators.csrf import csrf_exempt

from api import views as api_views

urlpatterns = [
    path("home/",include("expenses.urls")),
    path("admin/", admin.site.urls),
    path("api/",include("api.urls")),
    path('api-auth/', include('rest_framework.urls')),
    path('api-token-auth/', csrf_exempt(views.obtain_auth_token)),
]

Request:

Image of the request that fails in an error


The Question

Why is the csrf_exempt decorator being ignored by the obtain_auth_token view (which is a function-based view in DRF's implementation), even though I'm wrapping it in urls.py?

What is the definitive, robust way to disable CSRF protection only for the api-token-auth/ endpoint when SessionAuthentication is enabled in settings.py?

Note: I need to keep SessionAuthentication for the Browsable API.

When you are using SessionAuthentication, you are using Django's authentication which usually requires CSRF to be checked. Django REST Framework enforces this, only for SessionAuthentication, so you must pass the CSRF token in the X-CSRFToken header.

re : https://stackoverflow.com/a/26639895/16958410

if you want to use session Authentication best practice is to

pass the CSRF token in the X-CSRFToken header

as said in this post

not recommended

if you want SessionAuthentication but do not want to use csrf you can customize SessionAuthentication

example

from rest_framework.authentication import SessionAuthentication

class CsrfExemptSessionAuthentication(SessionAuthentication):
    def enforce_csrf(self, request):
        return  

view

from rest_framework.views import APIView
from rest_framework.permissions import AllowAny
from rest_framework.authtoken.views import ObtainAuthToken

class CustomAuthToken(ObtainAuthToken):
    authentication_classes = [CsrfExemptSessionAuthentication]
    permission_classes = [AllowAny]

also you can apply it on all project instead of one view like this

REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'path.to.CsrfExemptSessionAuthentication'
    ),
}

read about CSRF [MDN-doc] - [django-doc]

Вернуться на верх