Tuesday, February 26, 2013

Django, Ajax, Post, and CSRF

I was having a little trouble putting together a simple ajax request and response with Django. It seems like it should be simple task—just use the jquery ajax methods in the html page to call a simple view from the Django site. How hard can that be? Well it turns out that there are three major issues I had to overcome, but taking those into account it is actually very simple.

First, Django uses some middleware to avoid cross-site request forgery, which is essentially where another site pretends to be your site and submits requests to the URL that is designed to accept your form data. Attempts to make the request were failing with a 403 error, which means I was not authorized to make the request.

Second, Django has some logic in the dispatcher of the class-based-views that will not allow a POST request without specific code for the post. Normally this is a security benefit and a general convenience, and not something that has to be considered. But in this case I wanted to use POST so I had to choose the right method to override in my class based view.

Third, the template and view system of Django is designed to deliver templated data. Instead, I wanted to return raw JSON strings without all of the surrounding HTML and all of the rigamarole that comes with it. Fortunately, that is easily overriden in the class-based views.

A quick note on class-based views in Django:  Traditional views in Django were very simple--a function was defined for each view and and at the end it called a rendering method that passed a context with any data to send to the view along with the name of a template to fill with the data. It was stupid simple and allowed the building of dynamic web applications with very little code. But as the application got more complex, it turned out that there were lots of things that needed to be included in each view method, such as passing along session variables or setting headers or other things. In addition, a whole group of views might have a specific security or formatting characteristic in common that needed to be repeated over and over. These could be abstracted out into methods or decorators applied to the methods, but even those were tedious to use. The class-based views might seem slightly more complicated at first, but after a while their power and timesaving-abilities quickly become evident.

I should also note that I am using Django version 1.4.1. In searching for solutions to these issues, it was clear that the handling of CSRF has improved in each new version, and the older solutions outlined are not necessarily valid or necessary in version 1.4.1.

Solving the CSRF problem was the first hurdle. The problem could be solved by turning off the protection provided by the CSRF middleware, but why make it easier to exploit or bring down your web application? Especially when the solution is so simple. I went through several iterations of applying decorators to the sending and the receiving views, messing with headers, etc., and finally settled on the extremely simple task of including the CSRF token in the JSON data sent with the post request. Using jQuery, the javascript call to send the data (in this case, just a name and email) was simply:

$.ajax({
    url:"{% url process_request %}",
    data: {name:nameText,
            email:emailText,
            csrfmiddlewaretoken:'{{ csrf_token }}'
    ; },
    dataType: "json",
    type: "POST",
    success: successFunction,
    error: errorFunction

});

The key here is the csrfmiddlewaretoken data sent. That is basically all that is necessary to avoid the 403 error. No additional decorators or headers or cookies are needed.

The next hurdle was the question of the POST request. My page was successfully making the request, but my server was denying the request with a 405 error because the POST request was not the proper type. I eventually settled on a very simple use of the class-based views to properly process the data. The view that receives the data should be descended from the django.views.generic.View class. So we simply need this:

from django.http import HttpResponse

from django.views.generic import View

from json import dumps
class ProcessRequestInfoView(View):

    def post(self, context, **response_kwargs):
        name = self.request.POST.get('name','')
        email = self.request.POST.get('email','')
        ... do some work here processing the data 
            and make the response anything you like ...
        response = {"result":"OK","greeting":greeting} 
        return HttpResponse(dumps(response))

You don't need to do anything with the CSRF token...it is all handled automatically by the middleware. whatever you put in the "response" object is rendered into json and sent back to your success function as defined in the javascript.

The final issue, the need for POST is resolved by the view code above as well. Note that the "post" method is used to process the data. This tells Django to use a post request and that a post request is valid. And in the javascript above, note that we explicitly mark the dataType as JSON. These two things ensure that the POST request is allowed and processed, and that the return data is interpreted as JSON.

There is probably other stuff to do that is recommended which I don't know about, such as content-type headers or cookie settings, or whatever. If you know of something I left out, please drop me a comment! But given all the different info I found floating around about this, I am happy to report that this simple recipe works.

1 comment:

  1. Thanks a lot for this solution about 'csrftoken' and ajax with django pattern

    ReplyDelete