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

SvelteKit表单POST对接FastAPI OAuth2认证失败排查

SvelteKit表单POST认证请求FastAPI后端返回"field required"错误

问题详情

刚接触SvelteKit,使用表单POST action实现前端认证时遇到问题。相同的FastAPI后端代码在Next.js/React中可正常运行,但在SvelteKit中,后端返回“field required”错误,提示用户名和密码不能为空,怀疑问题出在fetch请求的body部分。


+page.server.js代码

import { fail, redirect } from '@sveltejs/kit';

/** @type {import('./$types').Actions} */
export const actions = {
    default: async ({ fetch, cookies, request }) => {
        const data = await request.formData();
  
        const username = data.get('username');
        const password = data.get('password');

        if (!username || !password) {
            return fail(400, { username, password, missing: true })
        }
  
        const response = await fetch(`${api_url}/login`, { 
            method: "POST",
            headers: { 
                'Content-Type': 'application/json', 
                "WWW-Authenticate": "Bearer",
                'Accept': 'application/json' 
            },
            body: JSON.stringify({username, password})
        })
  
        const json = await response.json();
  
        if (response.status === 200) {
            cookies.set('token', json.access_token, {
                path: '/',
                httpOnly: true,
                sameSite: 'strict',
                maxAge: 60 * 60 * 24 * 1000
            });
            throw redirect(302, "/");
        } else {
            return fail(422, { errors: json });
        }
        
    }
}

错误信息

msg: "field required"
type: "value_error.missing"

+page.svelte代码

<script>
import { enhance } from '$app/forms';

/** @type {import('./$types').ActionData} */
export let form;
</script>

<!-- component -->
<div class="w-full min-h-screen bg-cyan-950 text-white flex flex-col sm:justify-center items-center pt-6 sm:pt-0">
    <div class="w-full sm:max-w-md p-5 mx-auto">
      <h2 class="mb-12 text-center text-5xl font-extrabold">Accedi</h2>
      {#if form?.errors}<p class="error">{console.log(form.errors)}</p>{/if}
      <form method="POST" use:enhance>
        <div class="mb-4">
          <label class="block mb-1" for="email">Username</label>
          <input id="username" type="text" name="username" class="text-gray-700 py-2 px-3 border border-gray-300 focus:border-red-300 focus:outline-none focus:ring focus:ring-red-200 focus:ring-opacity-50 rounded-md shadow-sm disabled:bg-gray-100 mt-1 block w-full" />
        </div>
        <div class="mb-4">
          <label class="block mb-1" for="password">Password</label>
          <input id="password" type="password" name="password" class="text-gray-700 py-2 px-3 border border-gray-300 focus:border-red-300 focus:outline-none focus:ring focus:ring-red-200 focus:ring-opacity-50 rounded-md shadow-sm disabled:bg-gray-100 mt-1 block w-full" />
        </div>
        <div class="mt-6 flex items-center justify-between">
          <div class="flex items-center">
            <input id="remember_me" type="checkbox" class="border border-gray-300 text-red-600 shadow-sm focus:border-red-300 focus:ring focus:ring-red-200 focus:ring-opacity-50" />
            <label for="remember_me" class="ml-2 block text-sm leading-5 text-gray-200"> Ricordami </label>
          </div>
        </div>
        <div class="mt-6">
          <button class="w-full inline-flex items-center justify-center px-4 py-2 bg-cyan-600 border border-transparent rounded-md font-semibold capitalize text-white hover:bg-cyan-800 active:bg-red-700 focus:outline-none focus:border-red-700 focus:ring focus:ring-red-200 disabled:opacity-25 transition">Accedi</button>
        </div>
      </form>
    </div>
  </div>

FastAPI代码

@router.post("/login", response_model=Token, tags=["Auth"])
async def login_for_access_token(
    form_data: Annotated[OAuth2PasswordRequestForm, Depends()]
):
    user = authenticate_user(form_data.username, form_data.password)
    if not user:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Incorrect username or password",
            headers={"WWW-Authenticate": "Bearer"},
        )
    access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
    access_token = create_access_token(
        data={"sub": user.username}, expires_delta=access_token_expires
    )
    return {"access_token": access_token, "token_type": "bearer"}

问题原因及解决方法

FastAPI的OAuth2PasswordRequestForm依赖默认只接受application/x-www-form-urlencoded格式的请求体,而当前SvelteKit后端请求用的是application/json格式,导致FastAPI无法解析到用户名和密码字段,返回"field required"错误。

修改方案

把+page.server.js中的fetch请求改成发送表单格式数据,替换原fetch请求部分:

const response = await fetch(`${api_url}/login`, { 
    method: "POST",
    headers: { 
        "WWW-Authenticate": "Bearer",
        'Accept': 'application/json' 
        // 移除Content-Type: application/json,浏览器会自动设置为正确的表单格式头部
    },
    body: new URLSearchParams({ username, password }) // 用URLSearchParams构造表单数据
})

额外说明

  • Next.js/React中之前能正常工作,是因为当时的请求格式符合OAuth2PasswordRequestForm的要求(大概率是发送了表单格式数据)。
  • 若想让FastAPI支持JSON格式登录请求,可自定义Pydantic模型替代OAuth2PasswordRequestForm,但保持现有后端代码的情况下,修改前端请求格式是最简单的解决方案。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.25 19:53:12