Django: использование AJAX для входа в систему, как (следует) обновить CSRF-токен формы?
Контекст:
В проекте компании, над которым я работаю, используется AJAX для входа пользователя в систему для выполнения определенных действий на веб-странице. Эту веб-страницу можно просматривать без входа в систему, но определенные действия требуют аутентификации (например, обновление настроек оборудования). Я хочу сохранить форму, в которой пользователь сейчас находится, чтобы ему не пришлось повторно вводить свои изменения (что я сейчас и делаю, скрывая форму и накладывая на нее форму "login"), поэтому обновление страницы - это не вариант или, по крайней мере, крайняя мера.
Вопрос:
После завершения входа в систему CSRF-токен первой формы стал недействительным и его необходимо обновить. Это не вопрос "как добавить CSRF-токен в AJAX-запрос", а вопрос "как его обновить".
Django выдает следующую ошибку после входа в систему: Forbidden (CSRF token from POST incorrect.)
Вот что я уже пробовал:
Отправка нового CSRF-токена для формы после входа в систему (на стороне сервера):
form = AuthenticationForm(data=request.POST or None)
if form.is_valid():
# Login the user
user = authenticate(
username=form.cleaned_data.get("username"),
password=form.cleaned_data.get("password"),
)
if user is not None:
login(request, user)
# Neither of these have worked
# return JsonResponse(dict(success=True, csrfToken=request.POST.get("csrfmiddlewaretoken")))
# return JsonResponse(dict(success=True, csrfToken=str(csrf(request)["csrf_token"])))
На клиенте:
// From Django's AJAX documentation
function getCookie(name) {
let cookieValue = null;
if (document.cookie && document.cookie !== '') {
const cookies = document.cookie.split(';');
for (let i = 0; i < cookies.length; i++) {
const 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;
}
...
// Submit the login information and then update the page's CRSF token
// This is first and is run when the user is anonymous
$.ajax({
url: $("#login-form").attr("action"),
type: "POST",
data: data,
headers: {
'X-CSRFToken': getCookie("csrftoken"),
},
processData: false,
contentType: false,
success: function(data) {
// Update the new CSRF token
csrfToken = data["csrfToken"];
...
// Submit the settings form with (hopefully) an updated CSRF token
// This is second and is run when the user is authenticated
// Spoiler: it doesn't work
$.ajax({
url: $("#settings-form").attr("action"),
type: "POST",
data: data,
headers: {
'X-CSRFToken': getCookie("csrftoken"),
},
processData: false,
contentType: false,
Я на 100% признаю, что у меня нет большого опыта работы с AJAX или CSRF-токенами, так что если я делаю что-то не так, то это из-за моего недостатка опыта. Я не видел никаких других сообщений - нигде - относительно проблемы, подобной этой (99% были проблемой "как мне добавить CSRF токен"), но я готов посмотреть на любые, которые, по вашему мнению, могут быть релевантными.
Заранее благодарю вас за уделенное время.
Жюль
Благодаря @thebjorn в комментариях к моему посту выше, я смог определить, что я не обновил CSRF-токен на форме.
К сожалению, по какой-то причине, которую, я уверен, знает кто-то более умный, чем я, использование CSRF-токена, который я отправляю обратно клиенту в POST-запросе от формы входа, продолжает вызывать эту проблему. В качестве обходного пути я использую API fetch
и просто извлекаю новый из Django. Это, похоже, работает.
На будущее для тех, кто найдет этот пост, вот обновленный код AJAX для входа в систему, приведенный выше:
// Store the csrf token here and update this variable
// upon user login
var csrfToken = getCookie("csrftoken");
// Submit the login information and then update the page's CRSF token
// This is first and is run when the user is anonymous
$.ajax({
url: $("#login-form").attr("action"),
type: "POST",
data: data,
headers: {
'X-CSRFToken': csrfToken,
},
processData: false,
contentType: false,
success: function(data) {
// Update the new CSRF token
fetch(window.location.href, {
headers: {
"X-CSRFTOKEN-ONLY": "True" // Custom header
}
})
.then(response=>response.json())
.then(data_ => {
console.log(data_);
csrfToken = data_["csrftoken"];
$("#settings-form [name=csrfmiddlewaretoken]").val(data_["csrftoken"]);
...
Снова спасибо @thebjorn и @YevgeniyKosmak за помощь.