Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

CSRF and CORS with Django (REST Framework)

We're in the process of moving our frontend into a separate project (out of Django). It's a Javascript single page application.

One of the reasons is to make it easier for our frontend developers to do their work, not having to run the entire project -- including the API -- locally. Instead, we'd like them to be able to communicate with a test API we've set up.

We've managed to solve most of the CORS/CSRF issues along the way. But now we've run into something I can't find a solution for anywhere, despite reading lots of documentation and SO answers.

The frontend and the API are served from different domains (during development localhost and test-api.example.com). Until now, while served from the same domain, the frontend has been able to get the CSRF token from the csrftoken cookie set by the API (Django). But when served from different domains, the frontend (localhost) can't access the cookies of the API (api-test.example.com).

I'm trying to figure out a way to work around this, to somehow deliver the CSRF token to the frontend. The Django docs recommend to set a custom X-CSRFToken header for AJAX requests. Would we compromise the CSRF protection if we similarly served the CSRF token in every response as header and (via Access-Control-Expose-Headers) allowed this header to be read by the frontend?

Given that we've set up CORS properly for the API (i.e. only allowing certain domains to do cross origin requests to the API), JS on 3rd party sites should not be able to read this response header, thus not be able to make compromising AJAX requests behind the back of our users, right? Or did I miss something important here?

Or is there another, better way to achieve what we want?

like image 959
decibyte Avatar asked Mar 12 '18 16:03

decibyte


2 Answers

I didn't understand your question at first, so allow me to summarize: you can't get the CSRF token from the cookie on the client because the Same Origin Policy blocks you from accessing cross-domain cookies (even with CORS). So you're suggesting that the server transmit the cookie to the client in a custom header instead, and are wondering if that's secure.

Now, the documentation does make a suggestion for how to transmit the token if you're not using the cookie: put it in the response body. For example, you could use a custom meta tag. When it comes to security I lean towards using recommended solutions rather than trusting my own analysis of something new.

That caveat aside, I don't see any security problem with what you're suggesting. The Same Origin Policy will prevent a third-party site from reading the headers just as it will the body, and you can opt in to reading them from your client domain with the CORS Access-Control-Expose-Headers header.

You might find this answer interesting, as it lays out the advantages and disadvantages of various CSRF token schemes. It includes the use of a custom response header, and—to the point of your question—confirms: "If a malicious user tries to read the user's CSRF token in any of the above methods then this will be prevented by the Same Origin Policy".

(You might want to look into whether you need Django's CSRF protection at all with your SPA. See this analysis, for example. That's outside the scope of this question, though.)

like image 56
Kevin Christopher Henry Avatar answered Sep 20 '22 07:09

Kevin Christopher Henry


Assume you already have corsheaders installed. Write a Django middleware and include it in your MIDDLEWARE settings:

from django.utils.deprecation import MiddlewareMixin

class CsrfHeaderMiddleware(MiddlewareMixin):
    def process_response(self, request, response):
        if "CSRF_COOKIE" in request.META:
            # csrfviewmiddleware sets response cookie as request.META['CSRF_COOKIE']
            response["X-CSRFTOKEN"] = request.META['CSRF_COOKIE']
        return response

expose the header in your settings:

 CORS_EXPOSE_HEADERS = ["X-CSRFTOKEN"]

When you make a GET API call from you JS, you should get X-CSRFTOKEN from response header, go ahead and include it in the request header when you make POST PUT PATCH DELETE requests.

like image 36
XPX-Gloom Avatar answered Sep 18 '22 07:09

XPX-Gloom