Ticket #16936: ajax_csrf.patch

File ajax_csrf.patch, 6.0 KB (added by Idan Gazit, 13 years ago)

Docs patch with clearer AJAX & CSRF explanations.

  • docs/ref/contrib/csrf.txt

    diff --git a/docs/ref/contrib/csrf.txt b/docs/ref/contrib/csrf.txt
    index a683947..0e1935d 100644
    a b inconveniences: you have to remember to pass the CSRF token in as POST data with  
    8686every POST request. For this reason, there is an alternative method: on each
    8787XMLHttpRequest, set a custom `X-CSRFToken` header to the value of the CSRF
    8888token. This is often easier, because many javascript frameworks provide hooks
    89 that allow headers to be set on every request. In jQuery, you can use the
    90 ``ajaxSend`` event as follows:
     89that allow headers to be set on every request.
     90
     91As a first step, you must get a hold of the CSRF token itself. If you've got
     92a form which was rendered with the :ttag:`csrf_token`, then you can extract the
     93token from there:
     94
     95.. code-block:: javascript
     96
     97    // jQuery
     98    var csrftoken = $("input[name=csrfmiddlewaretoken]").val();
     99
     100If the CSRF token is not present in markup by use of the :ttag:`csrf_token`
     101template tag, it must be supplied to the client by other means. This is common
     102in cases where the form is dynamically added to the page, and Django supplies
     103a decorator which will set a cookie containing the CSRF token:
     104:func:`~django.views.decorators.csrf.ensure_csrf_cookie`. Use the decorator
     105on any view which will need access to the CSRF token, but doesn't include the
     106token in the actual markup sent to the client.
    91107
    92108.. code-block:: javascript
    93109
    94     $(document).ajaxSend(function(event, xhr, settings) {
    95         function getCookie(name) {
    96             var cookieValue = null;
    97             if (document.cookie && document.cookie != '') {
    98                 var cookies = document.cookie.split(';');
    99                 for (var i = 0; i < cookies.length; i++) {
    100                     var cookie = jQuery.trim(cookies[i]);
    101                     // Does this cookie string begin with the name we want?
    102                     if (cookie.substring(0, name.length + 1) == (name + '=')) {
    103                         cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
    104                         break;
    105                     }
     110    // once again, using jQuery
     111    function getCookie(name) {
     112        var cookieValue = null;
     113        if (document.cookie && document.cookie != '') {
     114            var cookies = document.cookie.split(';');
     115            for (var i = 0; i < cookies.length; i++) {
     116                var cookie = jQuery.trim(cookies[i]);
     117                // Does this cookie string begin with the name we want?
     118                if (cookie.substring(0, name.length + 1) == (name + '=')) {
     119                    cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
     120                    break;
    106121                }
    107122            }
    108             return cookieValue;
    109         }
    110         function sameOrigin(url) {
    111             // url could be relative or scheme relative or absolute
    112             var host = document.location.host; // host + port
    113             var protocol = document.location.protocol;
    114             var sr_origin = '//' + host;
    115             var origin = protocol + sr_origin;
    116             // Allow absolute or scheme relative URLs to same origin
    117             return (url == origin || url.slice(0, origin.length + 1) == origin + '/') ||
    118                 (url == sr_origin || url.slice(0, sr_origin.length + 1) == sr_origin + '/') ||
    119                 // or any other URL that isn't scheme relative or absolute i.e relative.
    120                 !(/^(\/\/|http:|https:).*/.test(url));
    121         }
    122         function safeMethod(method) {
    123             return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
    124123        }
     124        return cookieValue;
     125    }
     126    var csrftoken = getCookie('csrftoken');
    125127
    126         if (!safeMethod(settings.type) && sameOrigin(settings.url)) {
    127             xhr.setRequestHeader("X-CSRFToken", getCookie('csrftoken'));
    128         }
    129     });
     128The above code could be simplified by using the `jQuery cookie plugin
     129<http://plugins.jquery.com/project/Cookie>`_ to replace ``getCookie``:
    130130
    131 .. note::
     131.. code-block:: javascript
    132132
    133     Due to a bug introduced in jQuery 1.5, the example above will not work
    134     correctly on that version. Make sure you are running at least jQuery 1.5.1.
     133    var csrftoken = $.cookie('csrftoken');
    135134
    136 Adding this to a javascript file that is included on your site will ensure that
    137 AJAX POST requests that are made via jQuery will not be caught by the CSRF
    138 protection.
     135Finally, you'll have to actually set the header on your AJAX request, while
     136protecting the CSRF token from being sent to other domains:
    139137
    140 The above code could be simplified by using the `jQuery cookie plugin
    141 <http://plugins.jquery.com/project/Cookie>`_ to replace ``getCookie``, and
    142 `settings.crossDomain <http://api.jquery.com/jQuery.ajax>`_ in jQuery 1.5 and
    143 later to replace ``sameOrigin``.
     138.. code-block:: javascript
     139
     140    function safeMethod(method) {
     141        return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
     142    }
     143    $.ajaxSetup({
     144        beforeSend: function(xhr, settings) {
     145            if (!(/^https?:.*/.test(settings.url)) && safeMethod(settings.type) ) {
     146                // Only send the token to relative URLs i.e. locally.
     147                // Only send the token when using a safe HTTP method.
     148                // Using the csrftoken value acquired earlier.
     149                xhr.setRequestHeader("X-CSRFToken", csrftoken);
     150            }
     151        }
     152    });
     153
     154You can use `settings.crossDomain <http://api.jquery.com/jQuery.ajax>`_ in
     155jQuery 1.5 and newer in order to simplify the above code:
    144156
    145 In addition, if the CSRF cookie has not been sent to the client by use of
    146 :ttag:`csrf_token`, you may need to ensure the client receives the cookie by
    147 using :func:`~django.views.decorators.csrf.ensure_csrf_cookie`.
     157.. code-block:: javascript
     158
     159    $.ajaxSetup({
     160        crossDomain: false,
     161        beforeSend: function(xhr, settings) {
     162            if (safeMethod(settings.type) {
     163                xhr.setRequestHeader("X-CSRFToken", csrftoken);
     164            }
     165        }
     166    });
    148167
    149168The decorator method
    150169--------------------
Back to Top