Как переопределить 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.).

Вот как я его настроил:

  1. Я использую 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
  1. Я создаю конечную точку для получения этого токена:
...
from .views import get_csrf_token
...


urlpatterns = [
    ...
    path('csrf-token/', get_csrf_token, name='csrf_token'),
    ...
]
  1. У меня есть форма в файле +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>
  1. Я адаптировал код из 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);
}
  1. В моем 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;
}
  1. В моем +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.

Заранее спасибо!

Вернуться на верх