CSRF con Django, React + Redux usando Axios

Questo è un progetto educativo, non per la produzione. Non avevo intenzione di avere accessi utente come parte di questo.

Posso fare chiamate POST a Django con un token CSRF senza avere login utente? Posso farlo senza usare jQuery? Sono fuori dalla mia profondità qui, e sicuramente confondono alcuni concetti.

Per il lato JavaScript, ho trovato questo pacchetto redux-csrf . Non sono sicuro di come combinarlo con la mia azione POST usando 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")); }) } }; 

Dal lato Django, ho letto questa documentazione su CSRF, e questo generalmente funziona con le viste basate sulla class.

Ecco la mia opinione finora:

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

Ho provato ad usare il decoratore csrf_exempt solo per non csrf_exempt preoccupare di questo per ora, ma non sembra essere così che funziona.

Ho aggiunto {% csrf_token %} al mio modello.

Questo è il mio metodo getCookie (rubato da 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; } 

Ho letto che ho bisogno di cambiare le informazioni su CSRF di Axios:

 var axios = require("axios"); var axiosDefaults = require("axios/lib/defaults"); axiosDefaults.xsrfCookieName = "csrftoken" axiosDefaults.xsrfHeaderName = "X-CSRFToken" 

Dove posso attaccare il token vero e proprio, il valore che ottengo dal chiamare getCookie('csrftoken') ?

Ci sono tre modi. È ansible includere manualmente il token nell’intestazione di ogni chiamata axios, è ansible impostare axsos xsrfHeaderName in ogni chiamata oppure impostare un predefinito xsrfHeaderName .

1. Aggiungendolo manualmente

Diciamo che hai il valore del token memorizzato in una variabile chiamata csrfToken . Imposta le intestazioni nella tua chiamata axios:

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

2. Impostazione di xsrfHeaderName nella chiamata:

Aggiungi questo:

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

Quindi, nel file settings.py , aggiungi questa riga:

 CSRF_COOKIE_NAME = "XSRF-TOKEN" 

3. Impostazione delle intestazioni predefinite [1]

Anziché definire l’intestazione in ogni chiamata, è ansible impostare le intestazioni predefinite per gli assi.

Nel file in cui importi gli assiemi per effettuare la chiamata, aggiungi questo sotto le tue importazioni:

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

Quindi, nel file settings.py , aggiungi questa riga:

 CSRF_COOKIE_NAME = "XSRF-TOKEN" 

EDIT : Apparentemente funziona leggermente diverso con Safari [2]

[1] Dal commento di Dave Merwin


La confusione:

Django Docs

Innanzitutto, l’intero passaggio dai documenti di Django a cui faceva riferimento James Evans:

… su ogni XMLHttpRequest, imposta un’intestazione X-CSRFToken personalizzata sul valore del token CSRF. Questo è spesso più semplice, poiché molti framework JavaScript forniscono hook che consentono di impostare le intestazioni su ogni richiesta.

Come primo passo, devi ottenere il token CSRF stesso. L’origine consigliata per il token è il cookie csrftoken, che verrà impostato se hai abilitato la protezione CSRF per le tue visualizzazioni come descritto sopra.

Nota

Il cookie token CSRF è denominato csrftoken per impostazione predefinita, ma puoi controllare il nome del cookie tramite l’impostazione CSRF_COOKIE_NAME.

Il nome dell’intestazione CSRF è HTTP_X_CSRFTOKEN per impostazione predefinita, ma puoi personalizzarlo utilizzando l’impostazione CSRF_HEADER_NAME.


Axios Docs

Questo è dai documenti di Axios . Indica che imposti il ​​nome del cookie che contiene csrftoken e il nome dell’intestazione qui:

  // `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 

condizioni

Come indicato nella mia domanda, si accede ai cookie con document.cookie . L’unico cookie che ho è il token CSRF che ho inserito nel template Django. Ecco un esempio:

csrftoken=5knNceCUi9nL669hGGsvCi93XfqNhwTwM9Pev7bLYBOMXGbHVrjitlkKi44CtpFU

Ci sono alcuni concetti gettati in giro in quei documenti che creano confusione:

  • Il nome del cookie che contiene il token CSRF. In Django questo è di default csrftoken , che si trova sul lato sinistro del segno di uguale nel cookie.
  • Il token vero e proprio. Questo è tutto sul lato destro del segno di uguale nel cookie.
  • L’intestazione http che porta il valore del token.

Le cose che ho provato non funzionavano: 1 , 2

Ho scoperto che axios.defaults.xsrfCookieName = "XCSRF-TOKEN"; e CSRF_COOKIE_NAME = "XCSRF-TOKEN"

NON FUNZIONA IN APPLE Safari su Mac OS

La soluzione per MAC Safari è semplice, basta cambiare XCSRF-TOKEN in csrftoken

Quindi, nel codice js dovrebbe essere:

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

In settings.py:

  CSRF_COOKIE_NAME = "csrftoken" 

Il “modo semplice” ha quasi funzionato per me. Questo sembra funzionare:

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

E nel file settings.py:

 CSRF_COOKIE_NAME = "XCSRF-TOKEN" 

Questa configurazione funziona per me senza problemi 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 

Puoi aggiungere manualmente il token CSRF fornito da Django in tutte le tue richieste, ma è fastidioso.

Dai documenti di Django :

Mentre il metodo di cui sopra ( impostazione manuale del token CSRF ) può essere utilizzato per le richieste POST AJAX, presenta alcuni inconvenienti: è necessario ricordare di passare il token CSRF come dati POST ad ogni richiesta POST. Per questo motivo, esiste un metodo alternativo: su ogni XMLHttpRequest, imposta un’intestazione X-CSRFToken personalizzata sul valore del token CSRF. Questo è spesso più semplice, poiché molti framework JavaScript forniscono hook che consentono di impostare le intestazioni su ogni richiesta.

I documenti hanno il codice che puoi utilizzare per estrarre il token CSRF dal token cookie di CSRF e quindi aggiungerlo all’intestazione della tua richiesta AJAX.

In realtà c’è un modo davvero semplice per farlo.

Aggiungi axios.defaults.xsrfHeaderName = "X-CSRFToken"; alla tua app config e quindi imposta CSRF_COOKIE_NAME = "XSRF-TOKEN" nel tuo file settings.py. Funziona come un fascino.

Per me, Django non stava ascoltando le intestazioni che stavo inviando. Potrei arricciarsi nella API ma non potrei accedervi con gli assi. Dai un’occhiata al pacchetto cors-headers … potrebbe essere il tuo nuovo migliore amico.

L’ho risolto installando le intestazioni django-cors

 pip install django-cors-headers 

E poi aggiungendo

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

e

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

nel mio settings.py

Ho anche avuto

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

nel mio settings.py anche se questo è probabilmente eccessivo