13

Context: a chrome browser extension uses JQuery to request a response from a remote django app. Django recognizes that the request is made via AJAX and responds with "Hello AJAX!". I'm basing my exercise off this great example. Because this request is being made from a chrome extension, the request is being made cross site, so I've used the @CSRF_exempt decorator on my Django view.

Problem: My Django view is not recognizing the request as an AJAX request, and instead of responding Hello AJAX! it responds Hello not AJAX!.

My Django view:
(The url /xhr_test uses the following view)

@csrf_exempt
def check_login_extension(request):
    if request.is_ajax():
        message = "Hello AJAX!"
    else:
        message = "Hello not AJAX"
    return HttpResponse(message)

My JQuery request:

function xhrconnect() {
    $.get("http://localhost:8000/xhr_test", function(data) {
      document.getElementById('xhrmsg').innerHTML = (data);
    });
}
jchung
  • 903
  • 1
  • 11
  • 23
  • Try adding a trailing slash to the URL - `http://localhost:8000/xhr_test/`. – Daniel Roseman Oct 13 '11 at 14:48
  • Just tried, but no effect. Is there something in the way JQuery sends AJAX requests that requires a trailing slash? I know that `/xhr_test` is resolving properly to the correct view, because the view is responding with the message `Hello not AJAX`. If JQuery couldn't find the right url, there would be no message at all. – jchung Oct 13 '11 at 15:46

2 Answers2

15

Going through the jQuery source, it looks like $.ajax() (and therefore $.get(), $.post(), etc) will automatically set the crossDomain option to true if it sees that you're making a cross-domain request, which you are (relevant code here). And in the actual AJAX request, jQuery won't set the HTTP_X_REQUESTED_WITH header that Django needs for is_ajax() if crossDomain is set (relevant code here).

I think the easiest way to fix this is to explicitly set crossDomain to false:

function xhrconnect() {
    $.ajax({
        url: "http://localhost:8000/xhr_test", 
        success: function(data) {
            document.getElementById('xhrmsg').innerHTML = (data);
        },
        crossDomain: false
    });
}

If that doesn't work, you could try using an AJAX prefilter function to manually set the HTTP_X_REQUESTED_WITH header on the request.

nrabinowitz
  • 55,314
  • 10
  • 149
  • 165
  • This is a good explanation of the underlying problem. I tried setting crossDomain to false, but that didn't solve the problem. I'm guessing that either (a) the synax isn't right, or (b) the damage was already done, and setting crossDomain to false after the fact didn't reset the `HTTP_X_REQUESTED_WITH` header. Any other potential issues? I will try the AJAX prefilter function at some point next week and revert back. – jchung Oct 14 '11 at 20:25
  • 2
    Not sure why that doesn't work - seems to set the option correctly, though I don't know how to inspect the request headers: http://jsfiddle.net/nrabinowitz/MYSDd/2/ . One other option would be to pass in `headers: [{"X-Requested-With":"XMLHttpRequest"}]` as an option or in `$.ajaxSetup()`. – nrabinowitz Oct 14 '11 at 22:06
  • I know I'm late to the party, but why is this considered crossDomain if it's from his localhost domain to his localhost domain? – Ben G Feb 02 '12 at 21:02
  • 1
    @babonk - See the first paragraph, last sentence of the question. The request is being made from a browser extension, not from the `localhost` domain - in general, requests from an extension or a local file are considered cross-domain. – nrabinowitz Feb 05 '12 at 15:18
  • 1
    for the link to django documentation pertaining to is_ajax() and its header requirement: https://docs.djangoproject.com/en/dev/ref/request-response/#django.http.HttpRequest.is_ajax – rpq May 21 '13 at 05:17
  • Is this behavior planned to be fixed? – azmeuk Dec 24 '15 at 11:52
0

You may also want to take a look at this page. Because Django provides some protection against cross-site request forgeries (CSRF), it requires some special AJAX setup. I've included the AJAX setup below:

$(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'));
    }
});
NT3RP
  • 15,262
  • 9
  • 61
  • 97
  • Is this still an issue if the OP is using a `@csrf_exempt` decorator on the view? – nrabinowitz Oct 13 '11 at 16:57
  • That, I am not sure about. I will look into it, but I remember having problems with ajax requests until I included the above javascript code, and thought the answer might be valuable. – NT3RP Oct 13 '11 at 20:57
  • It's a little tricky to understand exactly what's going on in this code (my fault, not yours). Could you please explain how this setup works w.r.t. Django's CSRF protection? – jchung Oct 14 '11 at 20:27