You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何让SvelteKit的CSRF令牌匹配Django后端的CSRF令牌?

Django + SvelteKit + Allauth Headless CSRF令牌不匹配问题

后端基于Django开发,前端使用Svelte + SvelteKit,采用Allauth Headless实现认证流程。移除settings.py中的django.middleware.csrf.CsrfViewMiddleware后,注册功能可正常运行,确认问题仅由CSRF令牌不匹配导致,与CORS无关(已确保ALLOWED_HOSTS添加了localhost:5173)。

问题现象

  • Django后端和Svelte前端打印的CSRF令牌值完全一致,但浏览器控制台中请求头携带的csrftoken值却不同
  • Django控制台持续报错:Forbidden (CSRF token from the 'X-Csrftoken' HTTP header incorrect.)

当前配置详情

1. Django CSRF令牌获取视图(views.py)

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

2. Django URL路由配置

...
from .views import get_csrf_token
...

urlpatterns = [
    ...
    path('csrf-token/', get_csrf_token, name='csrf_token'),
    ...
]

3. Svelte注册表单(+page.svelte)

<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>
</div>
</div>
</div>

4. Allauth请求封装(参考React SPA示例)

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 } : {};
    // 此处打印的令牌与Django端输出完全一致
    console.log(`headers in signUp func: ${JSON.stringify(headers)}`); 
    return await request('POST', URLs.SIGNUP, data, headers, event);
}

5. Svelte端CSRF令牌获取逻辑(csrfCookie.ts)

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;
}

6. SvelteKit服务器端动作(+page.server.ts)

// +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' });
        }
    }
};

已尝试的解决方案及结果

  1. 禁用SvelteKit CSRF保护(修改svelte.config.js):
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;

结果:报错变为Forbidden (CSRF cookie not set.)

  1. 在表单中添加隐藏CSRF输入框:即使禁用SvelteKit CSRF保护,令牌仍会被自动替换,问题未解决。

求各位帮忙定位并解决这个CSRF令牌不匹配的问题!


内容的提问来源于stack exchange,提问作者St3ph3n92

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.20 03:29:53