13
votes

I have an application in Django 1.9 that uses SessionMiddleware. I would like to create an API for this application inside the same project, but when doing a POST request it does not work the @csrf_exempt annotation.

I am doing the requests throw Postman and this is what I have so far:

settings.py

MIDDLEWARE_CLASSES = [
    'corsheaders.middleware.CorsMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.cache.UpdateCacheMiddleware',
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'a9.utils.middleware.LocaleMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'a9.core.access.middleware.AccessMiddleware',
    'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
    'django.middleware.cache.FetchFromCacheMiddleware',    
]

OAUTH2_PROVIDER = {
    # this is the list of available scopes
    'SCOPES': {'read': 'Read scope', 'write': 'Write scope', 'groups': 'Access to your groups'}
}

CORS_ORIGIN_ALLOW_ALL = True
CORS_ALLOW_METHODS = (
    'DELETE',
    'GET',
    'OPTIONS',
    'PATCH',
    'POST',
    'PUT',
)
CORS_ALLOW_HEADERS = (
    'accept',
    'accept-encoding',
    'authorization',
    'content-type',
    'dnt',
    'origin',
    'user-agent',
    'x-csrftoken',
    'x-requested-with',
)

REST_FRAMEWORK = {
    # Use Django's standard `django.contrib.auth` permissions,
    # or allow read-only access for unauthenticated users.
    'DEFAULT_PERMISSION_CLASSES': [
        'rest_framework.permissions.DjangoModelPermissionsOrAnonReadOnly',
        #'rest_framework.permissions.IsAuthenticated',
    ],
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'oauth2_provider.ext.rest_framework.OAuth2Authentication',
        #'rest_framework.authentication.TokenAuthentication',
    )
}

urls.py

urlpatterns = [
    url(r'^v1/', include([
        url(r'^', include(router.urls)),
        url(r'^auth/', MyAuthentication.as_view()),
        url(r'^o/', include('oauth2_provider.urls', namespace='oauth2_provider')),
        url(r'^admin/', include(admin.site.urls)),
    ])),
]

views.py

@method_decorator(csrf_exempt, name='dispatch')
class MyAuthentication(TemplateView):

    def post(self, request, *args, **kwargs):

        return HttpResponse('Hello, World!')

After this I get always a CSRF verification failed error.

I asked this question in the IRC channel of django-rest-framework but I still have no answer. Please, any advice will be very appreciated.

4
Did you try importing View instead of TemplateView inside the class ?Vijesh Venugopal
There is no post method available for TemplateView.Vijesh Venugopal
Yes, that was the first attempt. Actually, TemplateView inherits from View, therefore it has a post method: docs.djangoproject.com/en/1.9/ref/class-based-views/base/…Carlos

4 Answers

7
votes

DO NOT USE csrf_exempt with Django REST framework.

This won't work because the SessionAuthentication enforces the csrf check anyway.

Please make sure you use the csrf token in your AJAX requests. Django has a comprehensive documentation about it

6
votes

I found out the way to solve this. You need to create a middleware that calls before any Session Middlewares and then check against your desired urls or app to exempt the CSRF token validation. So, the code would be like this:

settings.py

MIDDLEWARE_CLASSES = [
    'api.middleware.DisableCSRF',  # custom middleware for API
    'corsheaders.middleware.CorsMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.cache.UpdateCacheMiddleware',
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'a9.utils.middleware.LocaleMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'a9.core.access.middleware.AccessMiddleware',
    'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
    'django.middleware.cache.FetchFromCacheMiddleware',
]

urls.py

app_name = "api"

urlpatterns = [
    url(r'^v1/', include([
        url(r'^', include(router.urls)),
        url(r'^auth/', MyAuthentication.as_view()),
        url(r'^o/', include('oauth2_provider.urls', namespace='oauth2_provider')),
        url(r'^admin/', include(admin.site.urls)),
    ])),
]

csrf_disable.py

from django.core.urlresolvers import resolve
# django2

    
class DisableCSRF(object):
    """Middleware for disabling CSRF in an specified app name.
    """

    def process_request(self, request):
        """Preprocess the request.
        """
        app_name = "api"
        if resolve(request.path_info).app_name == app_name:
            setattr(request, '_dont_enforce_csrf_checks', True)
        else:
            pass  # check CSRF token validation

This will only check CSRF token against a specific app or url without removing all the CSRF. Also, this is django-rest-framework independent :)

5
votes

You need to decorate the csrf_exempt inside the dispatch method.

class MyView(FormView):

    @method_decorator(csrf_exempt)
    def dispatch(self, *args, **kwargs):
        return super(MyView, self).dispatch(*args, **kwargs)

    def post(self, request, *args, **kwargs):
        # ....
        return super(MyView, self).post(request, *args, **kwargs)
0
votes

In my case in django3.2 while using DRF, and function based view. I had to explicitly set permission_classes to []

@api_view(["GET"])
@permission_classes([])
def ping(*args, **kwargs):
    """
    Used to double check that the api is up an running.
    """
    return Response({"msg": "pong"}, status=200)

I believe a class based view might be similar:

class PingView(ListAPIView):
    permission_classes = []
    pagination_class = None
    serializer_class = None
    def get(self):
        return Response({"msg": "pong"}, status=200)