Nanor
Nanor

Reputation: 2550

CSRF token doesn't work with AJAX queries

I've tried following this guide to get a CSRF token to send with a POST request but I'm always getting a 403 error.

I have my home page at / which contains a link to open a modal. When the link is clicked the URL changes to /#modal. When the user types in an email/pass and clicks the post link it returns a 403 error with the message CSRF token missing or incorrect. My base page which contains the script imports and the modal looks like:

<!doctype html>
{% load staticfiles %}
<html class="no-js" lang="en">
<head>
  <meta charset="utf-8" />
  <meta http-equiv="x-ua-compatible" content="ie=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>Freelance Student</title>
  <link rel="stylesheet" href="{% static 'css/normalize.css'%}">
  <link rel="stylesheet" href="{% static 'css/style.css'%}" />
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.5.0/css/font-awesome.min.css">
    <link rel="stylesheet" href="{% static 'css/jquery.remodal.css' %}">
</head>
<body>
  <header class="site-header">
    <div class="container">
      <div class="site-logo two">
        <img src="{% static 'img/logo.png' %}" alt="">
      </div>

      <nav role="navigation" class="nav-collapse">
        <ul class="site-nav">
        <li><a href="#">Talent Search</a></li>
        <li><a href="#">Employers</a></li>
        <li><a href="#">About</a></li>
        <li><a href="#modal">Login</a></li>
        <li><a href="#modal2" class="button green">Create a profile</a></li>
      </ul>
    </nav>
    <div class="remodal" data-remodal-id="modal">
      <div class="login">
        <div class="favicon"><img src="{% static 'img/favicon.png' %}" /></div>
        <div class="login-iner">
          <form id="login" method="post" action="#">
                {% csrf_token %}
                {{login_form.as_p}}
              <a id="post" href="">post</a>
            <!--<input type="submit" value="Login" />-->
          </form>
        </div>
        <p>Forgot username/password? <span class="lspan"><a href="#">Click here</a></span></p>
      </div>
    </div>
    <div class="remodal" data-remodal-id="modal2">
      <div class="logot">
        <div class="favicon"><img src="images/favicon.png" /></div>
        <h1>Complete the form to build your profile</h1>
        <div class="logot-iner">
          <form>
          <p>
            My name is &nbsp;&nbsp;
            <input type="text" placeholder="first name" />
            &nbsp;&nbsp;
            <input type="text" placeholder="last name" />
            and I am a  &nbsp;&nbsp;<span class="type">
            <input type="text" placeholder="student type" />
            </span> &nbsp;&nbsp; Student
            <br />
            <br />
            I am completing a  &nbsp;&nbsp; <span class="type">
            <input type="text" placeholder="degree type" />
            </span> &nbsp;&nbsp; degree at<br />
            <span class="type1">
            <input type="text" placeholder="uni/college" />
            </span>
            <br />
            <br />
            I study/studied &nbsp;&nbsp; <span class="type2">
            <input type="text" placeholder="degree subject" />
            </span> &nbsp;&nbsp;and I
            am currently in my &nbsp;&nbsp;<span class="type">
            <input type="text" placeholder="select year" />
            </span> &nbsp;&nbsp; year.
            <br />
            <br />
            DOB: &nbsp;&nbsp; <span class="type3"><input type="text" placeholder="DD" /></span>&nbsp;&nbsp;
             <span class="type3"><input type="text" placeholder="MM" /></span> &nbsp;&nbsp;
              <span class="type3"><input type="text" placeholder="YYYY" /></span> &nbsp;&nbsp;
            <br />
            <br />
            My primary skill area is &nbsp;&nbsp; <span class="type">
            <input type="text" placeholder="skill area" />
            </span> &nbsp;&nbsp;<br />
            <br />
            <span class="lspan1">You must be at least 18 years of age to join</span><br />
            <br />
            <input type="submit" value="Sign up" />
          </p>
          </form>
        </div>
      </div>
    </div>
  </div>
  <!-- /.container -->
</header>
<!-- /.site-header -->
  {% block content %} {% endblock %}

<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
  <script src="{% static 'js/vendor/responsive-nav.min.js' %}"></script>
  <script src="{% static 'js/main.js' %}"></script>
  <script src="{% static 'js/base.js' %}"></script>
  <script src="{% static 'js/vendor/jquery.min.js' %}"></script>
  <script src="{% static 'js/jquery.remodal.js' %}"></script>

</body>
</html>

And the relevant JavaScript file looks like:

$(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;
    }

    $.ajaxSetup({
        beforeSend: function(xhr, settings) {
            if (!(/^http:.*/.test(settings.url) || /^https:.*/.test(settings.url))) {
                // Only send the token to relative URLs i.e. locally.
                xhr.setRequestHeader("X-CSRFToken", getCookie('csrftoken'));
            }
        }
    });


    $('a#post').on('click', function(e) {

        e.preventDefault();

        $.ajax({
            type : 'POST',
            url  : window.location.href,
            dataType: 'json',
            data: {'foo': 'bar'},
            beforeSend: function(xhr, settings) {
                console.log("Before Send");
                $.ajaxSettings.beforeSend(xhr, settings);
            },
            success: function(data){
                console.log(data);
            },
        });
    });
});

I don't understand what I'm missing? I've included {% csrf_token %} and the JavaScript is printing Before Send to the console.

Upvotes: 1

Views: 601

Answers (1)

Daniel Roseman
Daniel Roseman

Reputation: 599600

You've written your beforeSend function to only send the CSRF to local URLs, ie those that don't start with http or https. However, you've then generated the URL that your Ajax sends to by simply getting window.location.href which does include that string. You either need to use a relative URL, or change the logic in beforeSend.

Note also that you're not supposed to call that method directly; the whole point of calling ajaxSetup is that it does that automatically. Remove the beforeSend part of your actual .ajax call.

Upvotes: 1

Related Questions