0
votes

Using jQuery AJAX POST to call Django for an updated JSON set.

Have included both 'django.middleware.csrf.CsrfViewMiddleware' and 'django.middleware.csrf.CsrfResponseMiddleware' for the Django app and the code from https://docs.djangoproject.com/en/dev/ref/contrib/csrf/#ajax in my JS file.

$(document).ajaxSend(function(event, xhr, settings) {
    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]);
            // Does 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 sameOrigin(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 safeMethod(method) {
    return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
    }

    if (!safeMethod(settings.type) && sameOrigin(settings.url)) {
    xhr.setRequestHeader("X-CSRFToken", getCookie('csrftoken'));
    }
});

What happens

  1. Clear browser cookies and load the page.
  2. Django responds to the AJAX POST request with 403 forbidden, and then sets a cookie with the CSRF. But there is no JSON data.
  3. Trigger the AJAX POST request again
  4. Django receives the CSRF token from the X-CSRFToken header and responds appropriately with the JSON set.

The html and js files that are producing the ajax requests are NOT django produced. The js is interacting with django through jquery $.post() . So the only way to get a csrf token is to make a call to django before sending the post data.

It seems to me that the jQuery function solution given in the Django docs doesn't have a local cookie yet and is not waiting to get the cookie back from the server before it submits the POST request. But when it gets one along with the 403, it uses the (now locally stored) cookie for subsequent tries and works fine.

How can this be fixed so the cookie is set ahead of time and used by the AJAX POST request the first time around? Am I wrong to believe that django csrf can be made to work strictly from ajax calls originating on the same site?


*Other questions here relating to CSRF not working at all have suggested using ajax.Setup instead of ajaxSend, but according to this https://code.djangoproject.com/ticket/15284?cversion=0&cnum_hist=6 , that could cause problems with other JS, so ajaxSend was chosen.*

1

1 Answers

0
votes

It looks like middleware classes was placed in wrong order. To get right answer need more information,provide MIDDLEWARE_CLASSES from settings.py.

Add the middleware 'django.middleware.csrf.CsrfViewMiddleware' to your list of middleware classes, MIDDLEWARE_CLASSES. (It should come and before any view middleware that assume that CSRF attacks have been dealt with.)

Alternatively, you can use the decorator csrf_protect() on particular views you want to protect (see below).

Source: How to use CSRF protection