Ticket #16936: ajax_csrf_2.patch

File ajax_csrf_2.patch, 7.9 KB (added by Idan Gazit, 13 years ago)

Better CSRF docs patch, incorporates fixes from comments above.

  • docs/ref/contrib/csrf.txt

    diff --git a/docs/ref/contrib/csrf.txt b/docs/ref/contrib/csrf.txt
    index a683947..aabb57c 100644
    a b AJAX  
    8484While the above method can be used for AJAX POST requests, it has some
    8585inconveniences: 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
    87 XMLHttpRequest, set a custom `X-CSRFToken` header to the value of the CSRF
     87XMLHttpRequest, 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 the CSRF token itself. The recommended source for
     92the token is the ``csrftoken`` cookie, which will be set if you've enabled CSRF
     93protection for your views as outlined above.
     94
     95.. note::
     96
     97    The CSRF token cookie is named ``csrftoken`` by default, but you can control
     98    the cookie name via the :setting:`CSRF_COOKIE_NAME` setting.
     99
     100Acquiring the token is straightforward:
    91101
    92102.. code-block:: javascript
    93103
    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                     }
     104    // using jQuery
     105    function getCookie(name) {
     106        var cookieValue = null;
     107        if (document.cookie && document.cookie != '') {
     108            var cookies = document.cookie.split(';');
     109            for (var i = 0; i < cookies.length; i++) {
     110                var cookie = jQuery.trim(cookies[i]);
     111                // Does this cookie string begin with the name we want?
     112                if (cookie.substring(0, name.length + 1) == (name + '=')) {
     113                    cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
     114                    break;
    106115                }
    107116            }
    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));
    124117        }
     118        return cookieValue;
     119    }
     120    var csrftoken = getCookie('csrftoken');
     121
     122The above code could be simplified by using the `jQuery cookie plugin
     123<http://plugins.jquery.com/project/Cookie>`_ to replace ``getCookie``:
     124
     125.. code-block:: javascript
     126
     127    var csrftoken = $.cookie('csrftoken');
     128
     129.. note::
    125130
    126         if (!safeMethod(settings.type) && sameOrigin(settings.url)) {
    127             xhr.setRequestHeader("X-CSRFToken", getCookie('csrftoken'));
     131    The CSRF token is also present in the DOM, but only if explicitly included
     132    using :ttag:`csrf_token` in a template. The cookie contains the canonical
     133    token; the ``CsrfViewMiddleware`` will prefer the cookie to the token in
     134    the DOM. Regardless, you're guaranteed to have the cookie if the token is
     135    present in the DOM, so you should use the cookie!
     136
     137.. warning::
     138
     139    If your view is not rendering a template containing the :ttag:`csrf_token`
     140    template tag, Django might not set the CSRF token cookie. This is common in
     141    cases where forms are dynamically added to the page. To address this case,
     142    Django provides a view decorator which forces setting of the cookie:
     143    :func:`~django.views.decorators.csrf.ensure_csrf_cookie`.
     144
     145Finally, you'll have to actually set the header on your AJAX request, while
     146protecting the CSRF token.
     147
     148.. code-block:: javascript
     149
     150    function csrfSafeMethod(method) {
     151        // these HTTP methods do not require CSRF protection
     152        return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
     153    }
     154    function sameOrigin(url) {
     155        // test that a given url is a same-origin URL
     156        // url could be relative or scheme relative or absolute
     157        var host = document.location.host; // host + port
     158        var protocol = document.location.protocol;
     159        var sr_origin = '//' + host;
     160        var origin = protocol + sr_origin;
     161        // Allow absolute or scheme relative URLs to same origin
     162        return (url == origin || url.slice(0, origin.length + 1) == origin + '/') ||
     163            (url == sr_origin || url.slice(0, sr_origin.length + 1) == sr_origin + '/') ||
     164            // or any other URL that isn't scheme relative or absolute i.e relative.
     165            !(/^(\/\/|http:|https:).*/.test(url));
     166    }
     167    $.ajaxSetup({
     168        beforeSend: function(xhr, settings) {
     169            if (!csrfSafeMethod(settings.type) && sameOrigin(settings.url)) {
     170                // Send the token to same-origin, relative URLs only.
     171                // Send the token only if the method warrants CSRF protection
     172                // Using the CSRFToken value acquired earlier
     173                xhr.setRequestHeader("X-CSRFToken", csrftoken);
     174            }
    128175        }
    129176    });
    130177
    131 .. note::
     178You can use `settings.crossDomain <http://api.jquery.com/jQuery.ajax>`_ in
     179jQuery 1.5 and newer in order to replace the `sameOrigin` logic above:
    132180
    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.
     181.. code-block:: javascript
    135182
    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.
     183    function csrfSafeMethod(method) {
     184        // these HTTP methods do not require CSRF protection
     185        return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
     186    }
     187    $.ajaxSetup({
     188        crossDomain: false, // obviates need for sameOrigin test
     189        beforeSend: function(xhr, settings) {
     190            if (!csrfSafeMethod(settings.type)) {
     191                xhr.setRequestHeader("X-CSRFToken", csrftoken);
     192            }
     193        }
     194    });
    139195
    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``.
     196.. note::
     197
     198    In a `security release blogpost`_, a simpler "same origin test" example
     199    was provided which only checked for a relative URL. The ``sameOrigin``
     200    test above supersedes that example—it works for edge cases like
     201    scheme-relative or absolute URLs for the same domain.
    144202
    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`.
     203.. _security release blogpost: https://www.djangoproject.com/weblog/2011/feb/08/security/
    148204
    149205The decorator method
    150206--------------------
Back to Top