Reputation: 2695
I have searched through other similar questions but none of the solutions worked nor gave me any insight into what may be happening.
My setup is a Vue frontend (with its own routing) plus a Django backend and API. Any GET route I try works as expected, but POST ones require CSRF protection. I have a custom render function I call on the route that brings to the index (which is then going to be handled by Vue), where I provide the CSRF token like so:
def custom_render(request):
# ...
# from django.middleware.csrf
get_token(request)
# from django.shortcuts
return render(request, template)
This sets a cookie csrftoken
with the CSRF token, which seems to work correctly, as I can see it in the devtools and if I delete it, it's there again when I refresh. Here are my relevant Django settings.py
:
# This one is True in production, but for now I'm testing locally
CSRF_COOKIE_SECURE = False
CSRF_HEADER_NAME = "X-CSRFToken"
# I tried playing with both these options' values to no avail
CSRF_USE_SESSIONS = False
CSRF_COOKIE_HTTPONLY = False
MIDDLEWARE = [
"django.middleware.security.SecurityMiddleware",
"django.contrib.sessions.middleware.SessionMiddleware",
"django.middleware.common.CommonMiddleware",
"django.middleware.csrf.CsrfViewMiddleware",
"django.contrib.auth.middleware.AuthenticationMiddleware",
"django.contrib.messages.middleware.MessageMiddleware",
"django.middleware.clickjacking.XFrameOptionsMiddleware",
"django.middleware.common.BrokenLinkEmailsMiddleware",
]
On the Vue side (Typescript) I'm sending a request using this:
function getCsrfToken() {
return document.cookie.match("(^|;)\\s*" + "csrftoken" + "\\s*=\\s*([^;]+)")?.pop()
}
fetch(
"api/my-route/",
{
method: "POST",
headers: {
"X-CSRFToken": getCsrfToken(),
"Content-Type": "application/json",
"Accept": "application/json",
},
mode: "same-origin",
body: JSON.stringify(dataToSend),
},
)
This also works as intended, as I can see in the devtools that the request does contain the X-CSRFToken
header with the same content as the csrftoken
cookie. However, the response is nonetheless a 403 that claims the token is missing or incorrect. I'm not sure whether Django thinks it's missing or whether it thinks it's incorrect, so I'm unsure how to proceed. Wiring up a debugger is impractical because I don't know what internal method I should halt execution at, so I'm stuck.
EDIT: Other things I have tried recently:
get_token()
in my custom render function with the @ensure_csrf_cookie
decorator on my main view. Curiously, this did not result in the csrf cookie being set as expected.@csrf_protect
decorator to the main view. This did result in the csrf cookie being set, though I don't quite understand why. Anyway this didn't solve the issue.csrfmiddlewaretoken
field to the body of POST requests, with the token as value.Unfortunately none of these changed the outcome: when I do have the cookie available, its value doesn't seem to satisfy Django.
Upvotes: 3
Views: 1506
Reputation: 2695
I found the issue: the CSRF_HEADER_NAME = "X-CSRFToken"
in my settings does not take into account the fact that Django, so much for "explicit is better than implicit", implicitly normalises all header names such that the token in the request will end up looking like HTTP_X_CSRFTOKEN
, but doesn't bother doing the same to the custom name, so the two won't match by the time Django compares them.
Upvotes: 1