jQuery Ajax Cross Site Request Forgery Tokens with Python/Django

The Django tutorial demonstrates how to add a csrf token to a form, but how do we do it for Ajax?  The Django documentation explains how to do this. How does it work?  First, make sure that you set ‘django.middleware.csrf.CsrfViewMiddleware’, in your MIDDLEWARE_CLASSES in settings.py.  This will cause Django to set a csrf token as a cookie for each web page.  Other sites cannot access the cookie, but your page can.

One other thing stands out right away is this section.

In any template that uses a POST form, use the csrf_token tag inside the <form> element if the form is for an internal URL, e.g.:

<form action="." method="post">{% csrf_token %}

This should not be done for POST forms that target external URLs, since that would cause the CSRF token to be leaked, leading to a vulnerability.

We’ll get to proper form csrf protection, but first, let’s get it working for Ajax.  I took the code samples from the documentation and modified the code so that it will automatically get called for any Ajax submission.  I include this javascript file on any page that uses Ajax.  It is important to note that even though this file has a $(document).ready(function(){…}, you can include other javascript files with the same function.  They’ll all get called in order (assuming they download quickly enough).  Also, I am using C-style indentation so that my code sample will fit onto this page.

function csrfSafeMethod(method) {
    // these HTTP methods do not require CSRF protection
    return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
}

function sameOrigin(url) {
    // test that a given url is a same-origin URL
    // url could be relative or scheme relative or absolute
    var host = document.location.host; // host + port
    var protocol = document.location.protocol;
    var sr_origin = '/' + host;
    var origin = protocol + sr_origin;
    // Allow absolute or scheme relative URLs to same origin
    return 
        (url == origin || 
        url.slice(0, origin.length + 1) == origin + '/') ||
        (url == sr_origin || 
           url.slice(0, sr_origin.length + 1) == sr_origin + '/') ||
        // or any other URL that isn't scheme relative or 
        // absolute i.e relative.
        !(/^(\/\/|http:|https:).*/.test(url));
}

function setCookieInHeader (xhr, settings) {
   if (!csrfSafeMethod(settings.type) && sameOrigin(settings.url)) {
       // Send the token to same-origin, relative URLs only.
       // Send the token only if the method warrants CSRF protection
       // Using the CSRFToken value acquired earlier
       xhr.setRequestHeader("X-CSRFToken", $.cookie('csrftoken'));
   }
}

$(document).ready(function(){
    $.ajaxSetup({ beforeSend:setCookieInHeader });
});

(Update: You will need the jQuery cookie plugin for the $.cookie() function to work.)

Now that we’ve got csrf protection for Ajax submissions, we’ll have to work on proper form submissions next.

 

Comments are closed.