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')
?
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.
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},
// ...
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"
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:
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.
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
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:
csrftoken
, qui se trouve à gauche du signe égal dans le cookie.Les choses que j'ai essayées qui n'ont pas fonctionné: 1 , 2
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"
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
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"
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.
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.
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
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