Saturday, February 19, 2011

Fixing AJAX POST requests after Django update

I'm developing a personal project using Python Django and today I stumbled into a problem with Django's CSRF-protection mechanism and AJAX POST requests which took me a few (annoying) hours to get around.

As you probably know, to protect against Cross-site request forgery (CSRF) [1] Django uses a token mechanism. Every time you make a POST request to an internal URL, you have to provide the CSRF token as a POST parameter ('csrfmiddlewaretoken' field) which is checked by the CSRF middleware (django.middleware.csrf.CsrfViewMiddleware) and if the provided token doesn't match with the server-side token, you get a 403 error. The steps required to correctly enable CSRF protection are documented in Django's site [2]. In the same link, there is also a workaround for AJAX requests which weren't checked for CSRF and worked fine... until a couple of weeks ago.

This past week, I updated my Ubuntu system and suddenly all my AJAX POST requests stopped working. I checked [2] to see if everything was configured correctly and didn't find any errors. After some research, I discovered through Django's weblog that some engineers at Google discovered that a combination of browser plugins and redirects could allow AJAX requests to provide custom HTTP headers on a request to any website, making AJAX requests also a possible source of CSRF attacks [3]. On the same post, they also announced an update for Django's development trunk, 1.1 and 1.2 that pretty much breaks all your internal AJAX POST requests. They also provided a solution by including the CSRF token in the HTTP header on every AJAX request. For sake of simplicity, here is their proposed solution in jQuery:

$.ajaxSetup({
beforeSend: function(xhr, settings) {
if (!(/^http:.*/.test(settings.url)
|| /^https:.*/.test(settings.url))) {
// Only send the token to relative URLs i.e. locally.
xhr.setRequestHeader("X-CSRFToken",
$("#csrfmiddlewaretoken").val());
}
}
});

Now, there is just one problem with that: it doesn't work. Luckily, there is only a minor mistake there:

xhr.setRequestHeader("X-CSRFToken",
$("#csrfmiddlewaretoken").val());

Should be:

xhr.setRequestHeader("X-CSRFToken",
$('input[name="csrfmiddlewaretoken"]').val());


Also, don't forget to include the {% csrf_token %} tag in any html template that has an AJAX POST and use RequestContext in the associated views (steps 2 and 3 of [2]) like so:
from django.template import RequestContext

return render_to_response('my_page.html', some_dict,
context_instance=RequestContext(request))
[1] http://en.wikipedia.org/wiki/Cross-site_request_forgery
[2] http://docs.djangoproject.com/en/1.2/ref/contrib/csrf/#
[3] http://www.djangoproject.com/weblog/2011/feb/08/security/

1 comment:

  1. Hey there! Keep it up! This is a good read. I will be looking forward to visit your page again and for your other posts as well. Thank you for sharing your thoughts about developing and programing services in your area. I am glad to stop by your site and know more about developing and programing services.

    Bootstrap Themes - Bootstrap 3.0 - 100% Responsive

    ReplyDelete