web-dev-qa-db-fra.com

CSRF avec Django, React + Redux utilisant Axios

Ceci est un projet éducatif, pas pour la production. Je n'avais pas l'intention d'avoir des connexions d'utilisateurs dans le cadre de cette opération.

Puis-je effectuer des appels POST vers Django avec un jeton CSRF sans avoir de connexion utilisateur? Puis-je faire cela sans utiliser jQuery? Je suis hors de ma profondeur ici, et je suis sûrement en train de confondre certains concepts.

Pour le côté JavaScript, j'ai trouvé ce redux-csrf package. Je ne sais pas comment le combiner avec mon action POST à l'aide d'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"));
      })
  }
};

Du côté de Django, j'ai lu cette documentation sur CSRF, et this / travaillant généralement avec des vues basées sur les classes.

Voici mon point de vue jusqu'à présent:

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]))

J'ai essayé d'utiliser le csrf_exempt decorator juste pour ne pas avoir à me soucier de ça pour l'instant, mais ça ne semble pas être comme ça que ça marche.

J'ai ajouté {% csrf_token %} à mon modèle.

Voici ma méthode getCookie (volée dans la documentation Django):

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

J'ai lu qu'il me faut modifier les informations Axios CSRF:

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

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

Où dois-je coller le jeton réel, la valeur que j'obtiens en appelant getCookie('csrftoken')?

34
Reed Dunkle

Il y a trois façons. Vous pouvez inclure manuellement le jeton dans l'en-tête de chaque appel axios, définir la variable xsrfHeaderName d'axios dans chaque appel ou définir une variable xsrfHeaderName par défaut.

1. L'ajouter manuellement

Supposons que vous ayez la valeur du jeton stockée dans une variable appelée csrfToken. Définissez les en-têtes de votre appel axios:

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

2. Paramétrer xsrfHeaderName dans l'appel:

Ajoute ça:

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

Ensuite, dans votre fichier settings.py, ajoutez cette ligne:

CSRF_COOKIE_NAME = "XSRF-TOKEN"

3. Définition des en-têtes par défaut[1]

Plutôt que de définir l'en-tête dans chaque appel, vous pouvez définir les en-têtes par défaut pour axios.

Dans le fichier où vous importez axios pour effectuer l'appel, ajoutez ceci sous vos importations: 

axios.defaults.xsrfHeaderName = "X-CSRFToken";

Ensuite, dans votre fichier settings.py, ajoutez cette ligne:

CSRF_COOKIE_NAME = "XSRF-TOKEN"

Edit: Apparemment, cela fonctionne légèrement différemment avec Safari[2]

Edit2: Vous devrez peut-être aussi définir[3]:

axios.defaults.withCredentials = true

La confusion:

Django Docs

Tout d’abord, tout le passage de la Django docs que James Evans référencé :

... sur chaque XMLHttpRequest, définissez un en-tête X-CSRFToken personnalisé sur valeur du jeton CSRF. C'est souvent plus facile, car beaucoup de JavaScript les frameworks fournissent des crochets qui permettent de placer des en-têtes sur chaque demande.

Dans un premier temps, vous devez obtenir le jeton CSRF lui-même. Le recommandé la source du jeton est le cookie csrftoken, qui sera défini si comme indiqué ci-dessus, vous avez activé la protection CSRF pour vos vues.

Remarque

Le cookie de jeton CSRF est nommé par défaut csrftoken, mais vous pouvez contrôler le nom du cookie via le paramètre CSRF_COOKIE_NAME.

Le nom de l'en-tête CSRF est HTTP_X_CSRFTOKEN par défaut, mais vous pouvez personnalisez-le à l'aide du paramètre CSRF_HEADER_NAME.


Axios Docs

Ceci est du Axios docs . Cela indique que vous avez défini le nom du cookie qui contient la variable csrftoken, ainsi que le nom de l'en-tête ici:

  // `xsrfCookieName` is the name of the cookie to use as a value for xsrf token
  xsrfCookieName: 'XSRF-TOKEN', // default

  // `xsrfHeaderName` is the name of the http header that carries the xsrf token value
  xsrfHeaderName: 'X-XSRF-TOKEN', // default

Termes

Comme indiqué dans ma question, vous accédez aux cookies avec document.cookie. Le seul cookie que j'ai est le jeton CSRF que j'ai placé dans le modèle Django. Voici un exemple:

csrftoken=5knNceCUi9nL669hGGsvCi93XfqNhwTwM9Pev7bLYBOMXGbHVrjitlkKi44CtpFU

Quelques concepts sont jetés dans ces docs qui deviennent confus:

  • Nom du cookie contenant le jeton CSRF. Dans Django, il s'agit par défaut de csrftoken, qui se trouve à gauche du signe égal dans le cookie.
  • Le jeton réel. Tout ce qui se trouve à droite du signe d'égalité dans le cookie.
  • En-tête http contenant la valeur du jeton.

Les choses que j'ai essayées qui n'ont pas fonctionné: 1 , 2

30
Reed Dunkle

J'ai découvert que axios.defaults.xsrfCookieName = "XCSRF-TOKEN"; Et CSRF_COOKIE_NAME = "XCSRF-TOKEN"

NE FONCTIONNE PAS DANS Apple Safari sous Mac OS

La solution pour MAC Safari est simple, remplacez simplement XCSRF-TOKEN par csrftoken 

Donc, en js-code devrait être:

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

Dans settings.py:

    CSRF_COOKIE_NAME = "csrftoken"
12
yestema

Cette configuration fonctionne sans problème pour moi 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

6
krescruz

Le "moyen facile" a presque fonctionné pour moi. Cela semble fonctionner:

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

Et dans le fichier settings.py:

CSRF_COOKIE_NAME = "XCSRF-TOKEN"
3
cran_man

Vous pouvez ajouter manuellement le jeton CSRF fourni par Django dans toutes vos demandes de publication, mais c'est embêtant. 

Depuis le django docs

Bien que la méthode ci-dessus (définissant manuellement le jeton CSRF) puisse être utilisée pour les demandes AJAX POST, elle présente certains inconvénients: vous devez vous rappeler de transmettre le jeton CSRF comme POST données avec chaque demande POST. Pour cette raison, il existe une méthode alternative: sur chaque XMLHttpRequest, définissez un en-tête X-CSRFToken personnalisé sur la valeur du jeton CSRF. Cela est souvent plus facile, car de nombreux frameworks JavaScript fournissent des crochets permettant de définir des en-têtes sur chaque requête.

Les documents ont un code que vous pouvez utiliser pour extraire le jeton CSRF du cookie de jeton CSRF, puis l'ajouter à l'en-tête de votre demande AJAX. 

1
James Evans

Il existe en fait un moyen très simple de le faire.

Ajoutez axios.defaults.xsrfHeaderName = "X-CSRFToken"; à la configuration de votre application, puis définissez CSRF_COOKIE_NAME = "XSRF-TOKEN" dans votre fichier settings.py. Fonctionne comme un charme.

1
Dave Merwin

Pour moi, Django n'écoutait pas les en-têtes que j'envoyais. Je pouvais me glisser dans l’API mais je n’y pouvais pas accéder avec axios. Découvrez le paquet cors-headers ... cela pourrait être votre nouveau meilleur ami.

Je l'ai corrigé en installant Django-cors-headers

pip install Django-cors-headers

Et puis en ajoutant 

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

et 

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

dans mes settings.py

J'ai aussi eu

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

dans mon settings.py bien que ce soit probablement excessif

1
spinach

En plus de ce que dit yestema (et repris par krescruz, cran_man, Dave Merwin et autres), vous avez également besoin de:

axios.defaults.withCredentials = true
0
sureshvv