Search code examples
jqueryajaxdjangofirefoxdjango-csrf

(Django) CSRF Verification for AJAX requests working in Chrome but not Firefox


As the title states, my (Django) CSRF verification is working in Chrome but not Firefox and I'd like to know why so I can fix this.

I have this included in the head tag of my base.html file from which all other files in my application extend:

base.html, bottom of the head tag

    <script>
    $(document).ready(function() {

        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');
        function csrfSafeMethod(method) {
            // these HTTP methods do not require CSRF protection
            return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
        }
        $.ajaxSetup({
            beforeSend: function(xhr, settings) {
                if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
                    xhr.setRequestHeader("X-CSRFToken", csrftoken);
                }
            }
        });
    });
    </script>

And I have this code in a file called browse.js which needs to make ajax requests to my own server.

browse.js

Template = {
  setup : function(){
    Template.events.csrf();
    // etc. etc.
  },
  events: {
    csrf : function(){
      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');
      function csrfSafeMethod(method) {
        // these HTTP methods do not require CSRF protection
        return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
      }
      $.ajaxSetup({
        beforeSend: function(xhr, settings) {
          if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
            xhr.setRequestHeader("X-CSRFToken", csrftoken);
          }
        }
      });
    },
//etc. etc.
}
//The actual ajax request
Data = {
  api : {
    ajax_get_listings : function(cb){
      var g, i, o, _ref;
      _ref = [
        $('#ci').val(), 
        $('#co').val(), 
        $('#guests').val()], 
        i = _ref[0], 
        o = _ref[1], 
        g = _ref[2];
      if (g) {
        console.log('getting listings');
        return $.ajax({
          url:'/api/get_listing_items/', 
          type: 'POST',
          datatype:'json',
          data: {
            available_start_date: i,
            available_end_date: o,
            max_guests: g
          }, 
          success: function(d) {
            if (d.listings !== null){
              Data.listings._results = [];
              console.log(d);
              var l = $.parseJSON(
                $("<textarea/>").html(d.listings).text());
              console.log(l);
              data = l;
              console.log(data);
              return cb(data);
            }else{ 
              $('#ct').text('No listings found for your search criteria. Please keep searching!');
            }
          },
        });
      }
    },
  },
//etc. etc
}

Again, this works fine in Chrome. It only gives me a 403 Forbidden when I am on Firefox. Here is the traceback:

Traceback

Headers

view source
Content-Type    
text/html
Date    
Mon, 19 Oct 2015 22:06:07 GMT
Server  
WSGIServer/0.1 Python/2.7.3
Vary    
Cookie
X-Frame-Options 
SAMEORIGIN
view source
Accept  
*/*
Accept-Encoding 
gzip, deflate
Accept-Language 
en-US,en;q=0.5
Cache-Control   
no-cache
Connection  
keep-alive
Content-Length  
54
Content-Type    
application/x-www-form-urlencoded; charset=UTF-8
Cookie  
_ga=GA1.1.1619904474.1445292335; _gat=1; TawkConnectionTime=0;      __tawkuuid=e||127.0.0.1||mnW1PFpM4y26O8w
+2HatshrE3nWV4w3xD7SAtEMYGtV647bMojOwsqzNlPdxYCdB||2;     Tawk_560d98fcc096ea637ec4b8c0=vs15.tawk.to:443
||0
DNT 
1
Host    
127.0.0.1:8008
Pragma  
no-cache
Referer 
http://127.0.0.1:8008/properties/
User-Agent  
Mozilla/5.0 (Windows NT 6.1; WOW64; rv:41.0) Gecko/20100101 Firefox/41.0
X-CSRFToken 
null
X-Requested-With    
XMLHttpRequest  

Response

<!DOCTYPE html>
<html lang="en">
<head>
  <meta http-equiv="content-type" content="text/html; charset=utf-8">
  <meta name="robots" content="NONE,NOARCHIVE">
  <title>403 Forbidden</title>
  <style type="text/css">
    html * { padding:0; margin:0; }
    body * { padding:10px 20px; }
    body * * { padding:0; }
    body { font:small sans-serif; background:#eee; }
    body>div { border-bottom:1px solid #ddd; }
    h1 { font-weight:normal; margin-bottom:.4em; }
    h1 span { font-size:60%; color:#666; font-weight:normal; }
    #info { background:#f6f6f6; }
    #info ul { margin: 0.5em 4em; }
    #info p, #summary p { padding-top:10px; }
    #summary { background: #ffc; }
    #explanation { background:#eee; border-bottom: 0px none; }
  </style>
</head>
<body>
<div id="summary">
  <h1>Forbidden <span>(403)</span></h1>
  <p>CSRF verification failed. Request aborted.</p>


  <p>You are seeing this message because this site requires a CSRF cookie     when submitting forms.       This
 cookie is required for security reasons, to ensure that your browser is not     being hijacked by third
 parties.</p>
      <p>If you have configured your browser to disable cookies, please re-enable them, at least for this
 site, or for &#39;same-origin&#39; requests.</p>

</div>

<div id="info">
  <h2>Help</h2>
    <p>Reason given for failure:</p>
    <pre>
      CSRF cookie not set.
    </pre>
    <p>In general, this can occur when there is a genuine Cross Site Request Forgery, or when
  <a
  href='http://docs.djangoproject.com/en/dev/ref/contrib/csrf/#ref-contrib-   csrf'>Django's
  CSRF mechanism</a> has not been used correctly.  For POST forms, you need to
  ensure:</p>

      <ul>
      <li>Your browser is accepting cookies.</li>

    <li>The view function uses <a
     href='http://docs.djangoproject.com/en/dev/ref/templates/api/#subclassing-  context-requestcontext'
><code>RequestContext</code></a>
    for the template, instead of <code>Context</code>.</li>

    <li>In the template, there is a <code>{% csrf_token
%}</code> template tag inside each POST form that
targets an internal URL.</li>

   <li>If you are not using <code>CsrfViewMiddleware</code>, then you must   use <code>csrf_protect</code> on any views that use the <code>csrf_token</code>
template tag, as well as those that accept the POST data.</li>
</ul><p>You're seeing the help section of this page because you have <code>DEBUG =
  True</code> in your Django settings file. Change that to   <code>False</code>,
 and only the initial error message will be displayed.  </p>

   <p>You can customize this page using the CSRF_FAILURE_VIEW setting.</p>
</div>

</body>
</html>

What could be wrong?


SOLVED: I put @ensure_csrf_cookie on the view that was getting the cookie (NOT the function the ajax request was calling -- this had confused me). No more 403s on Firefox now. Yay


Solution

  • In your request headers, I see:

    X-CSRFToken null

    So my guess is that the cookie is being set in Firefox. Perhaps it was already set in Chrome from a previous session.

    The Django docs explain one reason why this may be:

    Warning

    If your view is not rendering a template containing the csrf_token template tag, Django might not set the CSRF token cookie. This is common in cases where forms are dynamically added to the page. To address this case, Django provides a view decorator which forces setting of the cookie: ensure_csrf_cookie().

    Try importing the ensure_csrf_cookie decorator in your views.py and wrapping your base view with it. Ex:

    from django.views.decorators.csrf import ensure_csrf_cookie
    
    @ensure_csrf_cookie
    def base_view(request):
        # do stuff
        return render('base.html', {...})
    

    I'm not sure if this is the root issue, but I hope this helps!