Working with CSRF protection in Django

David Chia
Aug 24, 2017 · 4 min read

It took me more than an hour today wrestling with CSRF protection in Django before getting it to work. Hopefully this short post would help anyone having similar problem.

What is CSRF and why do we need to protect our app from it?

CSRF stands for Cross-Site Request Forgery. It refers to attempts to send requests from an external site while pretending to be from the origin application. It is often used by attackers to execute malicious actions in the server.

Here is an example of what could happened if CSRF protection is not in place. Let’s say you have just logged in into your online bank application which has no CSRF protection. When you are logged in, the cookie used for authentication is stored on your browser. Then you visit a site that looks identical to your online bank application designed by a malicious attacker, which contains a link that sends a POST request to transfer your money to an external account. Not knowing that it is a malicious link, you clicked on it. Following that, a POST request is sent together with the stored cookie to transfer money out of your account. The application server received the cookie thinking that the request originated from the real user, and it authorises the transfer of funds, thereby resulting in losses.

Different sites or frameworks have different CSRF protection mechanisms. Django protects against CSRF attacks by generating a CSRF token in the server, send it to the client side, and mandating the client to send the token back in the request header. The server will then verify if the token from client is the same as the one generated previously; if not it will not authorise the request. Attackers won’t be able to access this token due to protection by the Same-Origin Policy.

Problem encountered with CSRF protection in Django

Now, let’s get to the problem that I faced when building a web application using Django and having to handle CSRF protection.

When I first made an AJAX call with a POST request, I got a HTTP 403 Forbidden error. A quick debug led me to the CSRF authorisation problem. The backend refused to authorise the request because there is no accompanying CSRF token to prove that the request is not from a foreign site.

So I tried the solution recommended by Django’s official site, which is to get the CSRF token included in Django template and set up AJAX to always include the CSRF token in its request header.

However, despite following the instruction closely, it says that CSRF token is null. So I turned to StackOverflow for help. Some users proposed using some cookie.js plugins; I tried them, but to no avail. Some users mentioned that Chrome does not store cookie when application is running locally; I tried a work around for that, but didn’t work either.

Taking one step back

After wasting more than half an hour trying different methods to no avail, I took a step back to read up on how CSRF protection really work in Django.

As it turned out, the root of the problem lies way upstream with how CSRF token is passed in to the template, instead of how it is accessed. On previous attempts, I simply assumed having to add a @ensure_csrf_cookie decorator to my POST method, and then the CSRF cookie will be readily accessibly from the page. However, on hindsight, I realised I did not understand how CSRF protection process work at all in Django.

To enable CSRF protection, Django creates a CSRF cookie to be passed in to a template. For POST/PUT/DELETE requests called from the template, they must be accompanied by a CSRF cookie that has been made available in the template, or else the requests won’t be authorised. Understanding this made it clear that @ensure_csrf_cookie should be added to the view method in the back end that is responsible for serving the HTML template where the POST request will be sent from. What I did, which was adding @ensure_csrf_cookie to the POST method didn’t make sense at all.

Realising that, I added @ensure_csrf_cookie to the GET method for the template, followed Django’s official recommendation for accessing CSRF token, and it works!

Conclusion

With so many packages and frameworks that abstract away implementation details, it is tempting to charge forward and write code as quickly as possible without understanding how things work. However, this small problem that I encountered shows how that may backfire and end up wasting more time. It is a good reminder to never take shortcuts and always aim to fully understand what we are doing with our code.


Code snippets

# views.pyclass HomepageView(TemplateView):
@method_decorator(ensure_csrf_cookie)
def get(self, request, **kwargs):
return render(request, ‘main/index.html’, context=None)
------------------------------------------------------------------// main.js (with some code adapted from Django official site)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]);
// Check if 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) && !this.crossDomain) {
xhr.setRequestHeader(“X-CSRFToken”, getCookie(‘csrftoken’));
}
}
});
// actual AJAX call
$.ajax({
method: “POST”,
url: “/postcodes”,
contentType: ‘application/json’,
data: JSON.stringify(input),
dataType: “json”,
success: function(data) {
// do something
}
})

davidchia

)

David Chia

Written by

davidchia

davidchia

On data, code and more

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade