自定义NextAuth登录表单触发[CLIENT_FETCH_ERROR]问题求助
NextAuth自定义登录页调用signIn出现CLIENT_FETCH_ERROR问题
问题描述
使用NextAuth搭建自定义登录页面时,调用signIn函数触发以下错误:
[next-auth][error][CLIENT_FETCH_ERROR], https://next-auth.js.org/errors#client_fetch_error Failed to fetch {error: {…}, url: '/api/auth/csrf', message: 'Failed to fetch'
已配置NEXTAUTH_URL但问题未解决,无法通过表单完成登录。不过NextAuth默认登录表单功能正常,检查NextAuthOptions配置未发现明显遗漏。
用户提供的配置信息如下:
package.json
{"scripts": {"dev": "next dev","build": "next build","start": "next start","lint": "next lint"},"dependencies": {"next": "^13.4.0","next-auth": "^4.22.1","react": "^18.2.0","react-dom": "^18.2.0","react-icons": "^4.8.0","react-quill": "^2.0.0","sass": "^1.62.1","zod": "^3.21.4"},"devDependencies": {"@types/node": "20.0.0","@types/react": "18.2.5","typescript": "5.0.4"}}
api/auth/[...nextauth]/route.ts
import NextAuth, { type NextAuthOptions } from "next-auth";import CredentialsProvider from "next-auth/providers/credentials";export const authOptions: NextAuthOptions = {secret: process.env.NEXT_PUBLIC_SECRET,providers: [CredentialsProvider({name: "Credentials",credentials: {username: { label: "username", type: "text", placeholder: "jsmith" },password: { label: "Password", type: "password" }},async authorize(credentials, req) {console.log("credentials", credentials);const user = { id: "1", name: "J Smith", email: "jsmith@example.com" };console.log("user", user);if (user) return user;else return null;}})],session: {strategy: 'jwt'},pages: {signIn: "/auth/signIn",},debug: true,};const handler = NextAuth(authOptions);export { handler as GET, handler as POST };
auth/signIn/page.tsx
'use client';import React from "react";import { signIn, useSession } from "next-auth/react";import styles from "./page.module.scss";import { redirect } from "next/navigation";export default function LoginPage() {const userNameRef = React.useRef<HTMLInputElement>(null);const passwordRef = React.useRef<HTMLInputElement>(null);const session = useSession();React.useEffect(() => {if (session.status === "authenticated") redirect("/admin");}, [session]);const onSubmit = async () => {const result = signIn("Credentials", { username: "jsmith", password: "1234" })console.log("value", result);}return (<div className={styles.login}><form className={styles.login__form}><h1 className={styles.login__form__title}>Login</h1><div className={styles.login__form__label__input}><label className={styles.login__form__label}>Username</label><input ref={userNameRef} className={styles.login__form__input} type="text"></input></div><div className={styles.login__form__label__input}><label className={styles.login__form__label}>Password</label><input ref={passwordRef} className={styles.login__form__input} type="password"></input></div><button onClick={onSubmit} className={styles.login__form__button}>Login</button></form></div>);}
.env
NEXT_PUBLIC_SECRET="784923748923748972938478fioswhjifuhef409283094823"NEXTAUTH_URL=http://localhost:3000
修复方案
1. 阻止表单默认提交行为
按钮点击会触发表单默认提交,导致页面刷新中断signIn异步请求,引发fetch失败。需在提交函数中阻止该行为。
2. 修正Secret环境变量配置
NextAuth的secret不能使用NEXT_PUBLIC_前缀的变量(该前缀变量会暴露给客户端,存在安全风险),需改为NEXTAUTH_SECRET。
3. 异步调用signIn时添加await
signIn是异步函数,未加await会导致无法正确获取结果,且可能引发异步流程错误。
4. 使用输入框实际值登录
原代码硬编码用户名密码,需通过ref获取输入框的真实值传入signIn。
5. 统一Provider名称大小写
signIn调用的provider名称需与配置中的CredentialsProvider内部名称一致(小写credentials)。
修改后的代码
修改后的.env
NEXTAUTH_SECRET="784923748923748972938478fioswhjifuhef409283094823" NEXTAUTH_URL=http://localhost:3000
修改后的api/auth/[...nextauth]/route.ts
import NextAuth, { type NextAuthOptions } from "next-auth"; import CredentialsProvider from "next-auth/providers/credentials"; export const authOptions: NextAuthOptions = { secret: process.env.NEXTAUTH_SECRET, providers: [ CredentialsProvider({ name: "Credentials", credentials: { username: { label: "username", type: "text", placeholder: "jsmith" }, password: { label: "Password", type: "password" } }, async authorize(credentials, req) { console.log("credentials", credentials); // 替换为实际用户校验逻辑,以下为示例 if (credentials?.username === "jsmith" && credentials?.password === "1234") { const user = { id: "1", name: "J Smith", email: "jsmith@example.com" }; console.log("user", user); return user; } return null; } }) ], session: { strategy: 'jwt' }, pages: { signIn: "/auth/signIn", }, debug: true, }; const handler = NextAuth(authOptions); export { handler as GET, handler as POST };
修改后的auth/signIn/page.tsx
'use client'; import React from "react"; import { signIn, useSession } from "next-auth/react"; import styles from "./page.module.scss"; import { redirect } from "next/navigation"; export default function LoginPage() { const userNameRef = React.useRef<HTMLInputElement>(null); const passwordRef = React.useRef<HTMLInputElement>(null); const { status } = useSession(); React.useEffect(() => { if (status === "authenticated") redirect("/admin"); }, [status]); const onSubmit = async (e: React.FormEvent) => { e.preventDefault(); const username = userNameRef.current?.value || ""; const password = passwordRef.current?.value || ""; const result = await signIn("credentials", { username, password, redirect: false // 可选,关闭自动跳转以便自行处理结果 }); console.log("登录结果", result); } return ( <div className={styles.login}> <form className={styles.login__form} onSubmit={onSubmit}> <h1 className={styles.login__form__title}>Login</h1> <div className={styles.login__form__label__input}> <label className={styles.login__form__label}>Username</label> <input ref={userNameRef} className={styles.login__form__input} type="text" /> </div> <div className={styles.login__form__label__input}> <label className={styles.login__form__label}>Password</label> <input ref={passwordRef} className={styles.login__form__input} type="password" /> </div> <button type="submit" className={styles.login__form__button}>Login</button> </form> </div> ); }
内容的提问来源于stack exchange,提问作者mato073
相关产品推荐
相关产品推荐

