16

I am using ajax request to send POST but it got response 403 because of csrf_token. I divide the frontend just using Vuejs and backend using Django to just reponse API only so I can't use Django template to render {% csrf_token %} or having csrftoken in session to use getcookie('csrftoken') like in Django's doc recommend. Is there anybody face this problem like me and got some solutions ? So thank you if you can help me this.

John T
  • 329
  • 1
  • 3
  • 13
  • Possible duplicate of [Django CSRF check failing with an Ajax POST request](http://stackoverflow.com/questions/5100539/django-csrf-check-failing-with-an-ajax-post-request) – Anonymous Apr 23 '17 at 03:33

3 Answers3

8

You can set the CSRF token in the header of your AJAX request. E.g., if you use jquery and jquery.cookie library, you can easily retrieve the Django-set csrftoken cookie like so:

$.ajax({
    url : 'YOUR_URL_HERE',
    headers: {'X-CSRFToken': $.cookie('csrftoken')},
    type: 'POST',
    dataType: 'json',
    data: {},
    success: function() {

    },
    error: function(xhr, errMsg, err) {
    },  
});

Django documentation also includes a section on this: https://docs.djangoproject.com/en/1.11/ref/csrf/#ajax

Please note that this solution may depend on your specific Django settings. The Django documentation link above details everything pretty clearly.

EDIT:

Given that even your initial page request is not served by Django, here is how you can accomplish what you're looking for...

1.) Create a view in your Django app that manually generates and returns a CSRF token (using django.middleware.csrf.get_token):

def get_csrf_token(request):
    token = django.middleware.csrf.get_token(request)
    return JsonResponse({'token': token})

2.) You would also need to add an appropriate entry in your Django URLs file:

url(r'^get-token/$', get_csrf_token)

3.) Then your Vue.js app can fetch the CSRF token using this endpoint. This doesn't need to be a user-initiated event; for example, you can configure your front-end app to fetch it on the $(document).ready() event. Then, using your preferred AJAX library (I am using jQuery in my example):

$.ajax({
    url: '/get-token/',
    type: 'GET',
    dataType: 'json',
    success: function(data) {
       $.cookie('csrftoken', data.token); // set the csrftoken cookie
    }
});

4.) Now your csrftoken cookie is set and should be usable for subsequent POST requests.

$.ajax({
    url : 'YOUR_URL_HERE',
    headers: {'X-CSRFToken': $.cookie('csrftoken')},
    type: 'POST',
    dataType: 'json',
    data: {},
    success: function() {

    },
    error: function(xhr, errMsg, err) {
    },  
});

I have used jQuery for AJAX functionality and the jQuery.cookie library for getting and setting cookies, but of course you can use whichever library you would prefer for these functions.

nb1987
  • 1,400
  • 1
  • 11
  • 12
  • 1
    No, it is not the solution. I wrote that I seperate between Django and Vuejs into frontend and backend so I can't have the csrftoken in session. I dont use Django template to render instead just using Vuejs. How can I create a new one that validate the server to not be blocked? – John T Apr 23 '17 at 04:40
  • Do you at least serve the initial page with Django? If so, could you not include the csrf_token there? – nb1987 Apr 23 '17 at 05:15
  • No i dont serve the initial page with Django as I wrote that I seperate front-end and back-end – John T Apr 23 '17 at 07:30
  • Okay, I have updated my response with a new solution. Please see under the 'EDIT' section of my response. – nb1987 Apr 23 '17 at 18:05
  • 1
    hi. i have a question lilke this? why dont we set-cookie into response of the server instead receiving csrf_token and the set it in javascript? – John T Apr 27 '17 at 08:32
  • That's a good question, and the answer is that it doesn't really matter--you can set the cookie on the server or on the client. The effect should be the same either way. – nb1987 Apr 27 '17 at 12:09
  • 1
    for some people it seems it's not a good idea to expose the token this way - https://github.com/pillarjs/understanding-csrf#exposing-your-csrf-token-via-ajax – Paolo May 19 '17 at 20:32
  • 10
    To add to what @Paolo brought up, this answer is a bad idea. It provides an endpoint for attackers to abuse. The point of CSRF tokens is to prevent bad actors from making requests on behalf of another user, since the bad actor cannot generate the CSRF token in order to fulfill the POST request. If you use this solution, attackers will be able to generate CSRF tokens at will and make fraudulent requests to the server. – Michael Hays Nov 12 '17 at 01:28
  • @MichaelHays No, it's not that bad an idea. CSRF tokens shouldn't ever be your only line of defence. Many frameworks use a CSRF or some other single use token and they are very, very often visible in source. You should be using some proper Auth protocols if you are worried about access. – ggdx Feb 15 '18 at 13:22
  • @ggdx If you're serving an endpoint for anyone to request a valid CSRF token, then what's the point of generating a token in the first place? – Michael Hays Feb 19 '18 at 17:04
  • @MichaelHays First line of defence, sure. Make sure that the request is from the place it's meant to come from. Like I said, **CSRF tokens shouldn't ever be your only line of defence. ... You should be using some proper Auth protocols if you are worried about access.** – ggdx Feb 20 '18 at 09:08
  • Right, I'm not disputing that. I'm saying that this accepted answer is a pointless approach that defeats the purpose of using CSRF tokens at all. So rather than implementing this solution, you may as well remove the requirement for CSRF tokens completely. – Michael Hays Feb 20 '18 at 17:53
  • @MichaelHays A malicious website wouldn't be able to access the proposed endpoint due to CORS restrictions. You could say the same is probably true for John T's app's POST requests, since it seems like he's building an SPA (thereby rendering the CSRF token unnecessary), but maybe his Django application serves both an SPA and a non-SPA. If the app supports older browsers (those without CORS restrictions), additional defenses are necessary. – nb1987 Feb 21 '18 at 21:53
  • @nb1987 Good point. Could you explain the scenario where the `/get-token/` approach would be useful in an app that _isn't_ an SPA? I'm struggling to understand the usefulness for situations where CSRF token protection is still necessary. – Michael Hays Feb 21 '18 at 22:40
  • @MichaelHays This example may seem tortured, but I can envisage the following workflow: (1.) user authenticates into an app, (2.) the landing page, on doc ready, fetches a token from `/get-token/`, and then sets it as the value of a cookie, say `csrftoken`, (3.) for the duration of the user's session, all subsequent POST requests, be they sent through AJAX or through form submission, will expect that the same value be present as a request header. A non-match yields HTTP 403. Since would-be attackers can't read the cookie, they can't send the *right* CSRF token even if they could generate one. – nb1987 Feb 22 '18 at 05:15
  • Also, I retract my previous suggestion that CSRF tokens aren't really needed for AJAX requests thanks to CORS restrictions. Apparently a combination of browser plugins and redirects can still introduce CSRF vulnerability. (E.g., see https://www.djangoproject.com/weblog/2011/feb/08/security/). I thank @MichaelHays for forcing me to put more research and thought into this matter. – nb1987 Feb 22 '18 at 05:24
  • Thanks for the example @nb1987. Hah, clearly I needed to put in the extra research, too. It's a surprisingly difficult problem to solve given how universal the threat is. – Michael Hays Feb 22 '18 at 17:47
1

According to the Django documentation you can simply use the ensure_csrf_cookie decorator on a view and that will send the cookie with the token with the response.

zeevb
  • 103
  • 6
  • 1
    This doesn't work in this scenario. Since the OP is getting the initial page from Vue.js (not Django), they cannot use `ensure_csrf_cookie` when loading that page. – touch my body Jan 02 '19 at 14:50
  • @touchmybody It will work just the same with vue, make it an endpoint in django and request that endpoint from vuejs – gigamarr Aug 15 '21 at 08:53
1

This is going to be wildly unpopular, but I've found it to be a relatively simple, secure and unobtrusive way to separate the front/backend.

In your VueJS app, you've probably got a login redirect when the user tries to access a page and are unauthenticated.

So instead of sending it to a vue router page, redirect it to /account/login/ (or some django app route - put an exception in cloudfront, or nginx proxy for /account/login/ to proxy pass to django) - then in the login.html template, just use a javascript window.location.href to your vueJS login page /login

The csrf_token will be set as a HttpOnly, secure cookie (which is what you want), and the disruption to the user is so minimal as to not even justify worrying about.

Trent
  • 2,909
  • 1
  • 31
  • 46
  • Why wildly unpopular? This is actually a good solution! – Jack022 Mar 23 '21 at 12:11
  • @Jack022 - well I know that, and I use this pattern all the time as JWTs very hard to secure properly. However, when I suggested this on github an issue was raised with local testing https://github.com/jazzband/django-rest-framework-simplejwt/issues/71#issuecomment-719970753 but I'm not really sure its valid, because testing would be done via request client, not django test Client – Trent Mar 23 '21 at 22:23
  • I actually had a similar idea a while ago and the problem was debugging locally indeed, i don't know how to run the Vue app and the Django app on the same port. I'm pretty sure that in production it wouldn't be so difficult though, i would just have to setup Nginx to route requests to the Django app or to the Vue app according to the route. May i ask you how are you handling it locally during development? Are you using webpack to load Vue inside of the Django app? – Jack022 Mar 23 '21 at 22:27
  • @Jack022 - everything I do locally is almost exactly the same as prod. Using docker-machine/docker-compose I configure nginx to proxy_pass routes to either Vue ('web' docker container) or django ('backend' docker container). Unit testing is done within itself, and integration tests are run separately bringing up both containers and tests are performed from a 3rd container properly testing the components as they are in the real world. https://python.plainenglish.io/django-start-building-an-api-in-8-minutes-part-1-6e901dcf8fcd <-- wrote an article about i – Trent Mar 25 '21 at 02:01