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?
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:
crossDomain
parameter of your AJAX request to True
. Sometimes, jQuery won't process the request if this isn't specified.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.If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With