Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to retrieve/provide a CSRF token to/from Django as an API

I'm working on a project that uses the Django REST Framework as a backend (let's say at api.somecompany.com but has a React.js frontend (at www.somecompany.com) not served by Django that makes AJAX requests.

I can't, therefore, use Django's traditional method of having the template include the CSRF token like this <form action="." method="post">{% csrf_token %}

I can make a request to Django REST Framework's api-auth\login\ url, which will return this header: Set-Cookie:csrftoken=tjQfRZXWW4GtnWfe5fhTYor7uWnAYqhz; expires=Mon, 01-Aug-2016 16:32:10 GMT; Max-Age=31449600; Path=/ - but I can't then retrieve this cookie to send back with my AJAX requests with X-CSRFToken (my understanding is of the separate subdomain), and it doesn't seem to be included automatically.

Here's my relevant code:

// using jQuery
function getCookie(name) {
    var cookieValue = null;
    if (document.cookie && document.cookie != '') {
        var cookies = document.cookie.split(';');
        for (var i = 0; i < cookies.length; i++) {
            var cookie = jQuery.trim(cookies[i]);
            // Does this cookie string begin with the name we want?
            if (cookie.substring(0, name.length + 1) == (name + '=')) {
                cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
                break;
            }
        }
    }
    return cookieValue;
}

function csrfSafeMethod(method) {
    // these HTTP methods do not require CSRF protection
    return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
}
$.ajaxSetup({
    beforeSend: function(xhr, settings) {
        if (!csrfSafeMethod(settings.type)) {
            xhr.setRequestHeader("X-CSRFToken", getCookie('csrftoken'));
        }
    }
});

As the page loads I call this to make sure I have a token:

$.ajax(loginUrl, { method: "OPTIONS", async: false })
    .done(function(data, textStatus, jqXHR) {
        console.log(jqXHR)
        app.csrftoken@ = $.cookie("csrftoken")
        console.log($.cookie("csrftoken"))
        console.log(app.csrftoken)
    })
    .fail(function(jqXHR, textStatus, errorThrown) {
        console.log(jqXHR)
    });

This isn't exactly clean but I haven't proven the concept to myself yet.

What is the 'correct' way of authenticating / protecting against CSRF when the frontend and backend are on different ports/domains?

like image 869
jimbofreedman Avatar asked Aug 03 '15 16:08

jimbofreedman


1 Answers

You can, as a matter of fact, make this work with CSRF protection, using a whitelist of allowed request origins.

To make this work, you're going to have to use Cross-Origin Resource Sharing (CORS). To achieve this, I'd recommend creating a middleware class that sets up your cross-sharing credentials. There's a great gist that outlines how to use some of the build in cross origin HTTP request headers. You can, if you want, insert the CSRF token via middleware in this way, by changing the origin reference values for the request.

The django-cors-headers app does this for you. You can see how they've negotiated the CSRF tokens in their middleware file, if you're interested.

Refer to the Django REST CORS Docs for more on this (they recommend using django-cors-headers).

If you're still having difficulties, try:

  1. setting the crossDomain parameter of your AJAX request to True. Sometimes, jQuery won't process the request if this isn't specified.
  2. if you're still not receiving a token header in the request at all, try appending the ensure_csrf_cookie() decorator around your view method. Occasionally, when there's no {% csrf_token %} template tag on the page (if you're not rendering a form), Django won't include the token in the request at all.
like image 187
ilkahnate Avatar answered Oct 19 '22 09:10

ilkahnate