Django AJAX: Passing form CSFR verification without proper token attachment. Why?
I have a small Django 5.1.1 project, in which I use an AJAX request to submit form data. I'm using {% csrf_token %} and have the setup for attaching the form csrfToken to the xhr header.
Problem: The CSFR verification is SUCCESSFUL even when xhr.setRequestHeader('X-CSRFToken', csrfToken); is commented out. The AJAX request goes through and gets the response. It only fails when the {% csrf_token %} is missing.
From what I understand it should fail without the form csfrToken attached to the header. What am I missing here?
Simplified code:
<form id="form1" method="post" class="...">
{% csrf_token %}
<div>
<label for="A" class=""></label>
<input type="text" name="symbol" id="A" class="...">
</div>
<div>
<button type="submit" id="submit-button"></button>
</div>
</form>
document.getElementById('form1').addEventListener('submit', function(event) {
event.preventDefault();
const xhr = new XMLHttpRequest();
const formData = new FormData(this);
const csrfToken = this.querySelector('[name=csrfmiddlewaretoken]').value;
xhr.open('POST', '/form-submit', true);
// xhr.setRequestHeader('X-CSRFToken', csrfToken);
xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
xhr.send(formData);
});
def index_view(request):
return HttpResponse("", status=200)
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",
]
You are sending the form data, so then the header is not necessary, since the csrf token is in the form data. Indeed, the {% csrf_token %}
template tag [Django-doc] translates to a hidden HTML input element, something like:
<input type="hidden" name="csrfmiddlewaretoken" value="---some-token---">
There is nothing special about that input element. It is just a HTML element like any other.
Your JavaScript functions encodes the entire form, so includes the item with the csrfmiddlewaretoken
. Django tries to find a CSRF token in the header, and in the POST data, and here it is in the POST data.
If you would remove the setHeader
and the {% csrf_token %}
, it will no longer work.
A lot of times however, AJAX requests do not encode form data, but just data they collect from somewhere else, for example from data-…
attributes of HTML elements. Therefore these AJAX requests, that thus don't work with a form that provides a CSRF token, need to manually set the header.