I want to use Next.js application as SPA in an integrated way with Laravel API. There is breeze-next library for this, but the operations performed here are client-side. I want to do auth operations server-side.
The code I wrote for this is below. The request I made to the /api/user endpoint returns the error message User Data { message: 'Unauthenticated.' }
. I don’t understand the reason, I would appreciate your help.
"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";
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>
);
}