17

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.

Carlos
  • 855
  • 2
  • 9
  • 18

4 Answers4

8

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

Linovia
  • 19,812
  • 4
  • 47
  • 48
  • 6
    As I mentioned in the question, I need to exempt the csrf token for the API application in order to allow other clients to consume the endpoints. The problem is not about AJAX requests, is about how to make that the Session Middleware works for other applications, i.e. mobiles, without removing it from the existing project. – Carlos Jan 18 '17 at 17:13
  • Then you get it wrong. Token authentication or OAuth should be used for non browser clients. – Linovia Jan 18 '17 at 21:10
  • 1
    I don't think you actually understand the question. An API is different than doing AJAX request. AJAX requests are for consuming the endpoints, NOT to create an API – Carlos Jan 18 '17 at 23:08
  • Actually I don't think you got my point. API will respond to various requests, some of which may be AJAX. My point is that AJAX requests should be authenticated with the SessionAuthentication while other machine requests (iOS, Android, curl, wget or others) should be authenticated with token. Note that *only* SessionAuth *requires* a CSRF. – Linovia Jan 19 '17 at 09:22
  • 1
    Sorry but I did, that's how I know that what you wrote in these comments it returns to the same question, does not solve it at all: How to make the CSRF disabled only for an API app?, and about the answer I found it already and post it as an answer in this same page. – Carlos Jan 19 '17 at 19:23
  • While I understand the OP was asking how to have an application within Django bypass the csrf check, I don't think any valid answer should suggest bypassing the check as it would leave a system vulnerable. This answer provides a link to official source that can be used to make the calls from within PostMan (or any AJAX request) work; and as @Linovia mentions there are alternative ways to ensure proper authentication for other clients. – darkrat Jan 20 '22 at 00:13
8

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 :)

jmunsch
  • 22,771
  • 11
  • 93
  • 114
Carlos
  • 855
  • 2
  • 9
  • 18
7

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)
Edwin Lunando
  • 2,726
  • 3
  • 24
  • 33
0

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)
jmunsch
  • 22,771
  • 11
  • 93
  • 114