Next.js Server Action对接Laravel Breeze API认证失败求助
问题排查:Next.js服务端认证调用Laravel /api/user返回"Unauthenticated"
我需要将Next.js应用作为SPA与Laravel API集成,现有breeze-next库可实现该集成,但它的认证操作是客户端的。我要实现服务端认证,编写了如下代码,但调用/api/user接口时返回错误信息User Data { message: 'Unauthenticated.' },恳请协助排查。
服务端认证代码("use server")
"use server"; import { cookies } from "next/headers"; import { z } from "zod"; import { redirect } from "next/navigation"; const schema = z.object({ email: z.string().email(), password: z.string().min(8), }); const getCookieValue = (name: string, cookies: string[]): string | null => { const cookieString = cookies.find((cookie) => cookie.startsWith(name + "=")); if (cookieString) { const value = cookieString.split(";")[0].split("=")[1]; return decodeURIComponent(value); } return null; }; export async function getCsrfToken() { const response = await fetch( `${process.env.NEXT_PUBLIC_BACKEND_URL}/sanctum/csrf-cookie`, { method: "GET", credentials: "include", } ); const cookieHeader = response.headers.get("set-cookie"); const setCookies = cookieHeader?.split(", "); if (setCookies) { const xsrfToken = getCookieValue("XSRF-TOKEN", setCookies); const sessionKey = getCookieValue("laravel_session", setCookies); if (xsrfToken && sessionKey) { cookies().set("XSRF-TOKEN", xsrfToken, { httpOnly: true, }); cookies().set("laravel_session", sessionKey, { httpOnly: true, }); return { xsrfToken, sessionKey }; } return { xsrfToken: null, sessionKey: null }; } } export async function authenticate(_currentState: unknown, data: FormData) { const formData = Object.fromEntries(data); const parsed = schema.safeParse(formData); const { xsrfToken, sessionKey } = (await getCsrfToken()) as { xsrfToken: string | null; sessionKey: string | null; }; const response = await fetch(`${process.env.NEXT_PUBLIC_BACKEND_URL}/login`, { method: "POST", headers: { "Content-Type": "application/json", Accept: "application/json", "X-XSRF-TOKEN": xsrfToken, Cookie: `XSRF-TOKEN=${xsrfToken};laravel_session=${sessionKey}`, } as HeadersInit, credentials: "include", body: JSON.stringify(parsed.data), }); const cookieHeader = response.headers.get("set-cookie"); const setCookies = cookieHeader?.split(", "); if (setCookies) { const xsrfToken = getCookieValue("XSRF-TOKEN", setCookies); const sessionKey = getCookieValue("laravel_session", setCookies); if (xsrfToken && sessionKey) { cookies().set("XSRF-TOKEN", xsrfToken, { httpOnly: true, }); cookies().set("laravel_session", sessionKey, { httpOnly: true, }); const response = await fetch( `${process.env.NEXT_PUBLIC_BACKEND_URL}/api/user`, { method: "GET", headers: { "Content-Type": "application/json", Accept: "application/json", "X-XSRF-TOKEN": xsrfToken, Cookie: `XSRF-TOKEN=${xsrfToken};laravel_session=${sessionKey}`, } as HeadersInit, credentials: "include", } ); const data = await response.json(); console.log("User Data", data); } } if (!response.ok) { return { message: "Login failed!" }; } redirect("/dashboard"); }
客户端登录页面代码("use client")
"use client"; import { authenticate } from "@/app/actions"; import { useFormState, useFormStatus } from "react-dom"; export default function Page() { const [state, dispatch] = useFormState(authenticate, { message: "", }); return ( <form action={dispatch}> <input type="email" name="email" placeholder="Email" required /> <input type="password" name="password" placeholder="Password" required /> <div>{state && <p>{state.message}</p>}</div> <LoginButton /> </form> ); } function LoginButton() { const { pending } = useFormStatus(); const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => { if (pending) { e.preventDefault(); } }; return ( <button disabled={pending} type="submit" onClick={handleClick}> {pending ? "Loading..." : "Login"} </button> ); }
核心问题排查与修复方案
1. Cookie的HttpOnly配置错误
Laravel的XSRF-TOKEN设计为非HttpOnly(前端需要读取该值设置请求头),而你代码中把XSRF-TOKEN设置为httpOnly: true,这会导致后续请求的XSRF验证失败。
修复代码:在getCsrfToken和authenticate函数中修改Cookie配置:
// 设置XSRF-TOKEN时改为非HttpOnly cookies().set("XSRF-TOKEN", xsrfToken, { httpOnly: false, secure: process.env.NODE_ENV === "production", sameSite: "lax", }); // laravel_session保持HttpOnly不变 cookies().set("laravel_session", sessionKey, { httpOnly: true, secure: process.env.NODE_ENV === "production", sameSite: "lax", });
2. Laravel端配置检查
- 确保
.env中SANCTUM_STATEFUL_DOMAINS包含Next.js的域名(例如localhost:3000) - 检查
config/cors.php中supports_credentials设置为true - 确认
routes/api.php中/api/user路由使用auth:sanctum中间件
3. 登录请求的参数验证
在authenticate函数中,需提前校验表单参数的合法性,避免无效请求:
const parsed = schema.safeParse(formData); if (!parsed.success) { return { message: "邮箱或密码格式错误!" }; }
4. 简化Cookie传递逻辑
服务端fetch请求中,无需手动拼接Cookie头,credentials: "include"会自动携带当前存储的Cookie,确保使用登录后返回的最新会话Cookie。
内容的提问来源于stack exchange,提问作者Ubeydullah Yılmaz
相关产品推荐
相关产品推荐

