Monkies for Ponies: CSRF, Django, Require (and Backbone/Marionette)


The Background

I'm a front end developer on a very small team in a very large multi-national company.  By necessity, I have to be able to provide my own server.  Sometimes that means just passing on some json requests, and sometimes it means building a complex calculation engine.  My server language of choice is Django (as you can probably tell by the pretty pink pony at the bottom of every page).  I know, I know...you're a Javascript guy, why not use Node.js? Because Python is awesome on the server, thats why.

Anyways, I also just started a new project.  This has allowed me to experiment with some libraries that I haven't had the opportunity to play with yet. Namely MarionetteJS and RequireJS which, I'm discovering, are both awesome.


The Problem

Django requires all POST and PUT requests to have a CSRF token (unless you turn off the CSRF Middleware...don't do that though).  Django's documentation provides some javascript code for attaching the token to each request.  Monkey-patch!

Prior to using Marionette and Require, I had a boilerplate file that I included with each app that had the code from that page just copy/pasted into it. However, Marionette basically made all of my Backbone boilerplate obsolete.  

With me being new to Require I had no idea how or where to place the code that inserts the CSRF token into my application's request headers.  It has to be after jQuery's loaded, but prior to any code execution. How do?


The 96.43% Solution

Most (say...96.43%) of the server calls that I'm going to make will use Backbone.sync. This means that you can simply extend Backbone.sync at runtime to insert the CSRF token. 

So here's what I came up with as a starting spot:

First we need the CSRF token. The code provided by the Django doc drives all over Cookie-land to get it.  I prefer to make life easier by letting the Django templating system provide me with anything I know I'm going to need.

Simply insert the following into any template that gets used on all your app's pages (most people have a base.html that they extend from):

<script>  
    var csrf_token = "{{ csrf_token }}";
</script>  

The csrf_token variable comes with the request object in Django, so we're just assigning it to a global variable.

Then, I just use Require's init callback to extend Backbone.sync to include my newly aquired token prior to actually sending the request, like so...

shim: {  
    ...
    'backbone': {
        deps: ['underscore', 'jquery'],
        exports: 'Backbone',
        init: function (underscore, jquery) {
            var originalSync = this.Backbone.sync;
            this.Backbone.sync = function(method, model, options) {
                options.beforeSend = function(xhr){
                    xhr.setRequestHeader('X-CSRFToken', window.csrf_token);
                };
                return originalSync(method, model, options);
            };
        }
    }
}
I had a much uglier form of this init function prior to finding https://gist.github.com/ferrouswheel/5078760. After finding it, I cleaned up my init to pretty much match his. 

However, occasionally, I'll need to make a server call without using Backbone.sync (ie to grab some one off info that has nothing to do with any of the models).  This method fails to take that into consideration.

Obtaining The Other 3.57%

So taking what I'd learned how to do above, why not take the code from the Django docs and go for a much more global approach?  First I tried putting the $.ajaxSetup code in an init function for jQuery in my shims.  Buzz. Wrong. As it turns out, jQuery doesn't actually ever use the shim config, thus no init function will ever be fired for it.

So the way to get around this is to place the code in an init function of a library that has jQuery as a dependency.  This ensures that jQuery is in fact loaded before you go about monkey-patching it.  In my case I just used my backbone shim section because I'm lazy.  

Here's the code: Note: the crossDomain attribute is only supported in Django 1.5 and up.  Consult the docs linked above.

shim: {  
    ...
    'backbone': {
        deps: ['underscore', 'jquery'],
        exports: 'Backbone',
        init: function (underscore, jquery) {
            $.ajaxSetup({
                crossDomain: false,
                beforeSend: function(xhr, settings) {
                    var csrfSafeMethod = function(method) { 
                        // these HTTP methods do not require CSRF protection
                        return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
                     };
                     if (!csrfSafeMethod(settings.type)) {
                         xhr.setRequestHeader("X-CSRFToken", window.csrf_token);
                      }
                  }
              });
        }
    }
}

One More Way

Okay, so what if you don't have anything that depends on jQuery to put this in.  Well, you can always do it the boring way of just putting the code into your main require function (I have mine in the same file as the require.config stuffs)  

Here's what it'd look like:

require(['mainApp', 'backbone', 'jqueryui'], function(app, Backbone, jqueryui){  
    /**
     * This runs once app.js has been loaded.
     */
    $(function(){
        $.ajaxSetup({
            crossDomain: false,
            beforeSend: function(xhr, settings) {
                var csrfSafeMethod = function(method) { 
                    // these HTTP methods do not require CSRF protection
                    return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
                };
                if (!csrfSafeMethod(settings.type)) {
                   xhr.setRequestHeader("X-CSRFToken", window.csrf_token);
                }
            }
        });
    });
    app.start();
});

Final Thoughts

Either of the last two ways will work well. Each has their use cases. I prefer my monky-patch in the init function. Why? Because it makes me feel smarter than just putting it in regular code.


comments powered by Disqus