Next.js 14中Next-Auth同时使用Credentials与Google Provider的问题
问题描述
我正在将一个MERN应用迁移至Next.js 14,但使用Next-Auth时遇到了以下问题:
- 通过独立注册表单创建的邮箱密码用户,在注释掉
authOptions中的callbacks后,邮箱密码登录可正常工作 - Google Provider能正常运行,用户不存在时会自动存入MongoDB
- 但保留
callbacks并尝试邮箱密码登录时,系统执行完Credentials的authorize逻辑后触发callbacks会引发异常,目前无法让GoogleProvider与CredentialsProvider协同工作
代码片段
/api/auth/[...nextauth]/route.js
import { authOptions } from "@/utils/authOptions"; import NextAuth from "next-auth"; const handler = NextAuth(authOptions); export { handler as GET, handler as POST };
authOptions 文件
import GoogleProvider from "next-auth/providers/google"; import CredentialsProvider from "next-auth/providers/credentials"; import connectDB from "@/lib/database"; import User from "@/models/User"; import bcrypt from "bcryptjs"; export const authOptions = { providers: [ CredentialsProvider({ name: "Credentials", credentials: { email: { label: "email:", type: "email", name: "email", placeholder: "your-email", }, password: { label: "password:", type: "password", name: "password", placeholder: "your-password", }, }, async authorize(credentials) { try { const foundUser = await User.findOne({ email: credentials.email, }).lean(); if (foundUser) { console.log("found user ", foundUser); console.log("User Exists"); const match = await bcrypt.compare( credentials.password, foundUser.password ); if (match) { console.log("Good Pass"); delete foundUser.password; foundUser["role"] = "Unverified Email"; return foundUser; } } } catch (error) { console.log(error); } return null; }, }), GoogleProvider({ clientId: process.env.GOOGLE_CLIENT_ID, clientSecret: process.env.GOOGLE_CLIENT_SECRET, authorization: { params: { prompt: "consent", access_type: "offline", response_type: "code", }, }, }), ], callbacks: { async signIn({ profile }) { await connectDB; const userExists = await User.findOne({ email: profile.email }); if (!userExists) { const username = profile.name.slice(" "); await User.create({ firstName: username[0], lastName: username[1], email: profile.email, image: profile.picture, }); } return true; }, async session({ session }) { const user = await User.findOne({ email: session.user.email }); session.user.id = user._id.toString(); return session; }, }, };
问题原因与解决方法
核心问题
- 邮箱密码登录触发
signIn回调时,profile参数为undefined——CredentialsProvider不会返回OAuth风格的profile对象,直接访问profile.email会报错 connectDB调用方式错误,应该执行函数await connectDB()而非直接await变量- 姓名分割逻辑错误,
slice(" ")无法正确拆分姓名
修复步骤
1. 修正signIn回调,区分登录来源
仅在Google登录时执行用户创建逻辑,避免Credentials登录时的报错:
async signIn({ user, account, profile }) { // 仅处理Google登录场景 if (account.provider === "google") { await connectDB(); // 调用数据库连接函数 const userExists = await User.findOne({ email: profile.email }); if (!userExists) { // 用split正确拆分姓名,兼容单字姓名情况 const username = profile.name.split(" "); await User.create({ firstName: username[0] || "", lastName: username[1] || "", email: profile.email, image: profile.picture, }); } } return true; }
2. 优化session回调的容错性
避免因用户不存在导致的报错,同时同步数据库中的用户角色:
async session({ session }) { await connectDB(); const user = await User.findOne({ email: session.user.email }); if (user) { session.user.id = user._id.toString(); session.user.role = user.role || "Unverified Email"; } return session; }
3. 完善Credentials的authorize逻辑
提前连接数据库,返回规范的用户结构:
async authorize(credentials) { await connectDB(); // 提前建立数据库连接 try { const foundUser = await User.findOne({ email: credentials.email }); if (!foundUser) return null; const match = await bcrypt.compare(credentials.password, foundUser.password); if (!match) return null; // 返回安全的用户信息,避免暴露敏感字段 return { id: foundUser._id.toString(), email: foundUser.email, name: `${foundUser.firstName} ${foundUser.lastName}`.trim(), role: foundUser.role || "Unverified Email", }; } catch (error) { console.error("Credentials auth error:", error); return null; } }
内容的提问来源于stack exchange,提问作者Stephen Scott Moore
相关产品推荐
相关产品推荐

