Как переопределить CSRF-токен SvelteKit, чтобы он соответствовал CSRF-токену бэкенда Django?
Для начала уточню, что я использую Django на бэкенде, а затем Svelte и SvelteKit на "фронтенде". Я также использую Allauth Headless для аутентификации.
Когда я удаляю django.middleware.csrf.CsrfViewMiddleware
из файла settings.py
, моя регистрация работает, как и ожидалось. Это наводит на мысль, что данная проблема вызвана только несоответствием CSRF, а не проблемами CORS, такими как отсутствие localhost:5173
в ALLOWED_HOSTS
.
Я ожидал, что CSRF-токен, выводимый как Django, так и Svelte, будет соответствовать токену, передаваемому в заголовках моих запросов. Однако, несмотря на то, что оба выведенных значения совпадают, заголовок запроса в инспекторе браузера имеет другое значение для csrftoken
. В консоли Django я получаю ошибку Forbidden (CSRF token from the 'X-Csrftoken' HTTP header incorrect.)
.
Вот как я его настроил:
- Я использую
get_token
вviews.py
для получения CSRF-токена:
from django.http import JsonResponse
from django.middleware.csrf import get_token
def get_csrf_token(request):
token = get_token(request)
print(f"CSRF Token: {token}")
response = JsonResponse({'csrfToken': token})
response.set_cookie('csrftoken', token)
return response
- Я создаю конечную точку для получения этого токена:
...
from .views import get_csrf_token
...
urlpatterns = [
...
path('csrf-token/', get_csrf_token, name='csrf_token'),
...
]
- У меня есть форма в файле
+page.svelte
, в которой есть действиеsignup
, которое будет выполняться в файле+page.server.ts
:
<script lang="ts">
import type { ActionData } from './$types';
export let form: ActionData;
</script>
<div class="container my-4">
<div class="row justify-content-center">
<div class="col-md-6">
<h2 class="text-center logo-font mb-4 text-pink mt-5">Sign Up</h2>
<hr class="w-25 mx-auto">
<form action="?/signup" method="POST" class="signup-form">
<div class="form-group">
<label for="username" class="--bs-pink1">Username</label>
<input type="username" name="username" id="username" class="form-control"
placeholder="Username" value={form?.username?? ''} required>
<small class="text-secondary">*Choose your username carefully, as it cannot be changed later on.</small>
</div>
<div class="form-group">
<label for="email" class="text-pink">Email</label>
<input type="email" name="email" id="email" class="form-control mb-3" placeholder="Email" value={form?.email ?? ''} required>
</div>
<div class="form-group">
<label for="password1" class="text-pink">Password</label>
<input type="password" name="password1" id="password1" class="form-control mb-3" placeholder="Password" required>
</div>
<div class="form-group">
<label for="password2" class="text-pink">Confirm password</label>
<input type="password" name="password2" id="password2" class="form-control mb-3"
placeholder="Confirm password" required>
</div>
{#if form?.error}
<div class="alert alert-danger mt-3">
<ul class="list-unstyled m-0">
<li>{form.error}</li>
</ul>
</div>
{/if}
<div class="form-actions text-center">
<button class="btn btn-lg btn-primary btn-block mt-4 -bs-purple" type="submit"
style="width: 80%">Confirm</button>
</div>
</form>
- Я адаптировал код из Allauth Headless React SPA Example:
import { getCsrfToken } from './csrfCookie';
...
const BASE_URL = `http://localhost:8000/_allauth/${CLIENT}/v1`;
const ACCEPT_JSON = {
accept: 'application/json'
};
...
export const URLs = Object.freeze({
...
SIGNUP: BASE_URL + '/auth/signup',
...
});
async function request(method, path, data = {}, headers = {}, event = null) {
const options = {
method,
headers: {
...ACCEPT_JSON,
...headers
},
credentials: 'include'
};
if (typeof data !== 'undefined') {
options.body = JSON.stringify(data);
options.headers['Content-Type'] = 'application/json';
}
const fetchFn = event ? event.fetch : fetch;
const resp = await fetchFn(path, options);
const msg = await resp.json();
return msg;
}
...
export async function signUp(data, event = null) {
const csrfToken = await getCsrfToken();
const headers = csrfToken ? { 'X-CSRFToken': csrfToken } : {};
// logs the same value as Django csrf token in console
console.log(`headers in signUp func: ${JSON.stringify(headers)}`);
return await request('POST', URLs.SIGNUP, data, headers, event);
}
- В моем
csrfCookie.ts
файле есть функция для получения токена, сгенерированного Django:
export async function getCsrfToken() {
const server_url = 'http://localhost:8000';
const response = await fetch(server_url + '/csrf-token/', {
credentials: 'include'
});
const data = await response.json();
const csrfToken = data.csrfToken;
console.log('CSRF token response:', data);
console.log('CSRF token for request:', csrfToken);
return csrfToken;
}
- В моем
+page.server.ts
файле я запускаю действиеsignup
, которое, в свою очередь, запускает функциюsignUp
:
// +page.server.ts
import { fail, redirect } from '@sveltejs/kit';
import type { Actions } from './$types';
import { signUp } from '$lib/allauth';
export const actions: Actions = {
signup: async ({ request, fetch }) => {
const data = await request.formData();
const username = data.get('username') as string;
const email = data.get('email') as string;
const password1 = data.get('password1') as string;
const password2 = data.get('password2') as string;
if (password1 !== password2) {
return fail(400, { error: 'Passwords do not match' });
}
try {
const response = await signUp({ username, email, password: password1 }, { fetch });
console.log('Full response:', response);
if (response.status === 200) {
return redirect(303, '/account');
} else if (response.status === 400) {
return fail(400, { error: response.errors[0].message });
} else {
return fail(500, { error: 'An error occurred' });
}
} catch (error) {
console.error('Signup error:', error);
return fail(500, { error: 'An error occurred' });
}
}
};
Если я отключаю CSRF-защиту SvelteKit в файле svelte.config.js
, я начинаю получать Forbidden (CSRF cookie not set.)
:
import adapter from '@sveltejs/adapter-node';
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
/** @type {import('@sveltejs/kit').Config} */
const config = {
kit: {
csrf: {
checkOrigin: false
},
adapter: adapter({
out: 'build',
preprocess: [vitePreprocess()]
})
}
};
export default config;
Я также пробовал загружать токен Django CSRF в скрытый ввод формы, но он также, похоже, заменяется, даже если CSRF отключен в SvelteKit.
Заранее спасибо!