58

This is an educational project, not for production. I wasn't intending to have user logins as part of this.

Can I make POST calls to Django with a CSRF token without having user logins? Can I do this without using jQuery? I'm out of my depth here, and surely conflating some concepts.

For the JavaScript side, I found this redux-csrf package. I'm not sure how to combine it with my POST action using Axios:

export const addJob = (title, hourly, tax) => {
  console.log("Trying to addJob: ", title, hourly, tax)
  return (dispatch) => {
    dispatch(requestData("addJob"));
    return axios({
      method: 'post',
      url: "/api/jobs",
      data: {
        "title": title,
        "hourly_rate": hourly,
        "tax_rate": tax
      },
      responseType: 'json'
    })
      .then((response) => {
        dispatch(receiveData(response.data, "addJob"));
      })
      .catch((response) => {
        dispatch(receiveError(response.data, "addJob"));
      })
  }
};

On the Django side, I've read this documentation on CSRF, and this on generally working with class based views.

Here is my view so far:

class JobsHandler(View):

    def get(self, request):
        with open('./data/jobs.json', 'r') as f:
            jobs = json.loads(f.read())

        return HttpResponse(json.dumps(jobs))

    def post(self, request):
        with open('./data/jobs.json', 'r') as f:
            jobs = json.loads(f.read())

        new_job = request.to_dict()
        id = new_job['title']
        jobs[id] = new_job

        with open('./data/jobs.json', 'w') as f:
            f.write(json.dumps(jobs, indent=4, separators=(',', ': ')))

        return HttpResponse(json.dumps(jobs[id]))

I tried using the csrf_exempt decorator just to not have to worry about this for now, but that doesn't seem to be how that works.

I've added {% csrf_token %} to my template.

This is my getCookie method (stolen from Django docs):

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 = cookies[i].trim();
            // 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;
}

I've read that I need to change the Axios CSRF info:

var axios = require("axios");
var axiosDefaults = require("axios/lib/defaults");

axiosDefaults.xsrfCookieName = "csrftoken"
axiosDefaults.xsrfHeaderName = "X-CSRFToken"

Where do I stick the actual token, the value I get from calling getCookie('csrftoken')?

Reed Dunkle
  • 3,408
  • 1
  • 18
  • 29
  • You would require to read the value of "csrftoken" cookie and set the same value as header to "X-CSRFToken". The implementation of getting the cookie and setting header would change if you don't want to use jquery, but the basic fundamentals remains the same. – shubham003 Aug 31 '16 at 17:34

9 Answers9

62

This Q&A is from 2016, and unsurprisingly I believe things have changed. The answer continues to receive upvotes, so I'm going to add in new information from other answers but leave the original answers as well.

Let me know in the comments which solution works for you.

Option 1. Set the default headers

In the file where you're importing Axios, set the default headers:

import axios from 'axios';
axios.defaults.xsrfHeaderName = "X-CSRFTOKEN";
axios.defaults.xsrfCookieName = "csrftoken";

Option 2. Add it manually to the Axios call

Let's say you've got the value of the token stored in a variable called csrfToken. Set the headers in your axios call:

// ...
method: 'post',
url: '/api/data',
data: {...},
headers: {"X-CSRFToken": csrfToken},
// ...

Option 3. Setting xsrfHeaderName in the call:

Add this:

// ...
method: 'post',
url: '/api/data',
data: {...},
xsrfHeaderName: "X-CSRFToken",
// ...

Then in your settings.py file, add this line:

CSRF_COOKIE_NAME = "XSRF-TOKEN"

Edit (June 10, 2017): User @yestema says that it works slightly different with Safari[2]

Edit (April 17, 2019): User @GregHolst says that the Safari solution above does not work for him. Instead, he used the above Solution #3 for Safari 12.1 on MacOS Mojave. (from comments)

Edit (February 17, 2019): You might also need to set[3]:

axios.defaults.withCredentials = true

Things I tried that didn't work: 1

Reed Dunkle
  • 3,408
  • 1
  • 18
  • 29
  • It was a few years ago that I was working on this, and when I discovered approach #2. I haven't used Django since. If there is stale information in my answer, let me know and I'll update it. – Reed Dunkle Mar 06 '19 at 00:17
  • Option 3 works for me, but not the option presented below for Safari. – Greg Holst Apr 17 '19 at 14:24
  • 1
    @ReedDunkle Yes it looks fine also on Safari 12.1 on MacOS Mojave. At first I did not try your option3 as I thought I go straight to yestema's answer, but that was obviously a wrong way for me. – Greg Holst Apr 18 '19 at 08:18
  • @ReedDunkle.. Thought I had solved this issue. But it has resurfaced. See https://stackoverflow.com/questions/55842141/csrf-verification-fails-in-production-for-cross-domain-post-request – sureshvv Apr 25 '19 at 05:51
  • Interestingly I found "X-CSRFTOKEN" in axios translates to "HTTP_X_CSRFTOKEN" on the server request header. – sureshvv Apr 25 '19 at 05:58
  • 1
    Option 1 worked for me perfectly. – Arche Pattern Dec 03 '22 at 20:26
24

I've found out, that axios.defaults.xsrfCookieName = "XCSRF-TOKEN"; and CSRF_COOKIE_NAME = "XCSRF-TOKEN"

DOESN'T WORK IN APPLE Safari on Mac OS

The solution for MAC Safari is easy, just change XCSRF-TOKEN to csrftoken

So, in js-code should be:

    import axios from 'axios';
    axios.defaults.xsrfHeaderName = "X-CSRFTOKEN";
    axios.defaults.xsrfCookieName = "csrftoken";

In settings.py:

    CSRF_COOKIE_NAME = "csrftoken"
yestema
  • 6,932
  • 4
  • 23
  • 29
  • 9
    [`CSRF_COOKIE_NAME`](https://docs.djangoproject.com/en/2.1/ref/settings/#csrf-cookie-name) defaults to `'csrftoken'`, so you don't need to edit `settings.py` – Michael Hays Nov 19 '18 at 19:25
  • 1
    Definitely should be the correct answer! – Rui Oliveira Dec 26 '21 at 19:48
  • This is what made it work for me. I was using axios & django – YaDa Jan 19 '22 at 17:27
  • This solution worked for me as well with setting `CSRF_COOKIE_HTTPONLY = False` in django settings.py which is `False` by default in Django, but set to `True` e.g., in django-cookiecutter. – sir_dance_a_lot Sep 29 '22 at 10:01
15

This configuration works for me without problems Config axios CSRF django

import axios from 'axios'

/**
 * Config global for axios/django
 */
axios.defaults.xsrfHeaderName = "X-CSRFToken"
axios.defaults.xsrfCookieName = 'csrftoken'

export default axios
krescruz
  • 418
  • 4
  • 7
8

After spending too many hours researching, and implementing the above answer, I found my error for this problem! I have added this answer to be supplemental of the accepted answer. I had set up everything as mentioned, but the gotcha for me was actually in the browser itself!

If testing locally, make sure you are accessing react through 127.0.0.1 instead of localhost! localhost handles request headers differently and doesn't show the CSRF tokens in the header response, where as 127.0.0.1 will! So instead of localhost:3000 try 127.0.0.1:3000!

Hope this helps.

Mark N
  • 326
  • 2
  • 13
5

The "easy way" almost worked for me. This seems to work:

import axios from 'axios';
axios.defaults.xsrfHeaderName = "X-CSRFTOKEN";
axios.defaults.xsrfCookieName = "XCSRF-TOKEN";

And in the settings.py file:

CSRF_COOKIE_NAME = "XCSRF-TOKEN"
cran_man
  • 51
  • 1
  • 1
  • 1
    Welcome to SO! Please visit [how to write an answer](http://stackoverflow.com/help/how-to-answer) You have provided an answer to an old question that already has a very detailed answer, approved and upvoted by many. Does your *almost works* answer provide anything useful to others that has yet not been detailed? – Olaia Mar 23 '17 at 20:31
2

You could add the Django-provided CSRF token manually into all of your post requests, but that's annoying.

From the Django docs:

While the above method (manually setting CSRF token) can be used for AJAX POST requests, it has some inconveniences: you have to remember to pass the CSRF token in as POST data with every POST request. For this reason, there is an alternative method: on each XMLHttpRequest, set a custom X-CSRFToken header to the value of the CSRF token. This is often easier, because many JavaScript frameworks provide hooks that allow headers to be set on every request.

The docs have code you can use to pull the CSRF token from the CSRF token cookie and then add it to the header of your AJAX request.

James Evans
  • 830
  • 8
  • 12
2

There is actually a really easy way to do this.

Add axios.defaults.xsrfHeaderName = "X-CSRFToken"; to your app config and then set CSRF_COOKIE_NAME = "XSRF-TOKEN" in your settings.py file. Works like a charm.

Dave Merwin
  • 1,382
  • 2
  • 22
  • 44
1

For me, django wasn't listening to the headers that I was sending. I could curl into the api but couldn't access it with axios. Check out the cors-headers package... it might be your new best friend.

I fixed it by installing django-cors-headers

pip install django-cors-headers

And then adding

INSTALLED_APPS = (
    ...
    'corsheaders',
    ...
)

and

MIDDLEWARE = [  # Or MIDDLEWARE_CLASSES on Django < 1.10
    ...
    'corsheaders.middleware.CorsMiddleware',
    'django.middleware.common.CommonMiddleware',
    ...
]

into my settings.py

I also had

ALLOWED_HOSTS = ['*']
CORS_ORIGIN_ALLOW_ALL = True
CORS_ALLOW_CREDENTIALS = True
CORS_EXPOSE_HEADERS = (
    'Access-Control-Allow-Origin: *',
)

in my settings.py although that is probably overkill

spinach
  • 89
  • 1
  • 5
1

In addition to what yestema said (and echoed by krescruz, cran_man, Dave Merwin et. al), You also need:

axios.defaults.withCredentials = true
sureshvv
  • 4,234
  • 1
  • 26
  • 32