5

I've developed an Django file upload API which receive the posted data from client and save the data as file.

According to the Django CSRF manual, the HTTP request header should set X-CSRFToken with the csrftoken cookie value. I've set the X-CSRFToken by the code below, but the POST request still forbidden(403) by Django server as the picture below shows.

$(document).ready(function(){
    var authid
    $.get("http://localhost:8000/v1/getAuthID?username=testuser1&password=123", function(data){
        authid = data["authid"];
        var csrftoken = $.cookie('csrftoken');
        console.log(csrftoken);

        $.ajaxSetup({
            beforeSend: function(xhr, settings) {
                xhr.setRequestHeader("X-CSRFToken", csrftoken);
            }
        });

        url = "http://localhost:8000/v1/file".replace("{authid}", authid).replace("{token}", csrftoken)
        $.post(url, function(data){
        })
    })
})

enter image description here

How did you overcome the Django CSRF by send POST request to Django server?

Thanks!

ybdesire
  • 1,593
  • 1
  • 20
  • 35
  • 2
    What makes you so sure it's an CSRF issue? I don't see anything wrong with your CSRF handling at first glance. – knbk Aug 13 '15 at 10:25
  • I got the "CSRF verification failed. Request aborted." when I checked the 403 response. And I can get the same response when the X-CSRFToken adding code removed. – ybdesire Aug 13 '15 at 11:15
  • 2
    Ok, can you check if the cookie sent in the POST request matches the header? – knbk Aug 13 '15 at 11:26
  • Thanks @knbk for the checking! I can never get a cookie named "csrftoken " if I cleared the previous cookie at browser. – ybdesire Aug 13 '15 at 11:38
  • It seems Django did not send cookie csrftoken to client – ybdesire Aug 13 '15 at 12:01
  • I guess you're not using `{% csrf_token %}` in any template? In that case, try to decorate your views with [`@ensure_csrf_cookie`](https://docs.djangoproject.com/en/1.8/ref/csrf/#django.views.decorators.csrf.ensure_csrf_cookie). It should be on the view that handles the GET request _before_ the POST request. – knbk Aug 13 '15 at 12:06
  • Yes, I just develop API by Django. And my front-end is written by HTML&JS, no Django template used. – ybdesire Aug 13 '15 at 12:07
  • I've set the ensure_csrf_cookie, and Django server sent the cookie by HTTP header at "Set-Cookie". But the cookie from header cannot set browser cookie, so $.cookie('csrftoken') didn't work. Finally, I use @csrf_exempt to tell the view not check csrf token. Since my API should be accessed by another domain with POST/GET request. There is security concern by this way, so I will enhance my API security by other methods. Thanks all for the help! – ybdesire Aug 14 '15 at 09:57

2 Answers2

1

Move the ajaxSetup outside of your AJAX success function as it is required for the request, not after it.

$(document).ready(function(){
    var authid;
    var csrftoken = $.cookie('csrftoken');
    console.log(csrftoken);

    $.ajaxSetup({
        beforeSend: function(xhr, settings) {
            xhr.setRequestHeader("X-CSRFToken", csrftoken);
        }
    });


    $.get("http://localhost:8000/v1/getAuthID?username=testuser1&password=123", function(data){
        authid = data["authid"];

        url = "http://localhost:8000/v1/file".replace("{authid}", authid).replace("{token}", csrftoken)
        $.post(url, function(data){
        })
    })
})
justcompile
  • 3,362
  • 1
  • 29
  • 37
  • Thanks for the help:) But still got 403 error with your code. – ybdesire Aug 13 '15 at 08:09
  • 1
    Is it a typo that your console screen shot is requesting localhost:8003 but your code snippet is localhost:8000 or is that the intension? – justcompile Aug 13 '15 at 08:17
  • The JS is run at the local website of localhost:8003. And Django server run at localhost:8000. – ybdesire Aug 13 '15 at 08:18
  • I think the problem would be that unless you're using a csrf token / cookie generated by the Django server on :8000, you won't get around this issue as you are performing cross site requests. It'll work locally, but in production, unless you're using the same domain / sub-domains then you'll have issues – justcompile Aug 13 '15 at 16:23
  • Thanks @justcompile, the CORS have been enabled by Access-Control-Allow-Origin set for * . So I think this is not cross domain issue. – ybdesire Aug 14 '15 at 01:32
  • I'm not 100%, but I'm pretty sure CORS are a browser thing, so wouldn't have any affect on CSRF – justcompile Aug 14 '15 at 13:57
1

I use the javascript code below that takes care of the whole CSRF topic for ajax calls:

// This function gets cookie with a given name
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;
}
var csrftoken = getCookie('csrftoken');

/*
The functions below will create a header with csrftoken
*/

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));
}

$.ajaxSetup({
    beforeSend: function(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", csrftoken);
        }
    }
});

Just include it after JQuery.

Danial Tz
  • 1,884
  • 1
  • 15
  • 20
  • Thanks @Danial for the code : ). getCookie('csrftoken') is not work for my situation. I didn't use Django template, and I communicate with the API developed by Django only by JS. So I cannot get the cookie 'csrftoken'. It seems this is because the response cookie got by Ajax cannot set browser cookie. I'm still researching it... ... – ybdesire Aug 14 '15 at 01:45