NextAuth中Google登录signin回调未触发,数据迁移方案存疑
我在Next.js项目中使用NextAuth、MongoDB及Google Provider,Google登录时NextAuth会自动将{name, email, id, image, emailVerified}字段写入users集合并作为会话返回。我希望将users集合中的name和image字段移除,添加到usersdetails集合中,以user:user_id作为关联users集合的引用字段(即userdetails: {users:userId(ref: users), name:"sth", image:"url"})。为此我在signin回调中编写了数据迁移代码,但该回调未触发——服务器控制台中连console.log都未输出。
我的api/auth/[...nextauth]/route.js代码如下:
import dbConnect from "@/lib/dbConnect"; import clientPromise from "@/lib/mongodb"; import Users from "@/models/useraccounts/users"; import UsersDetails from "@/models/useraccounts/usersDetail"; import { MongoDBAdapter } from "@auth/mongodb-adapter"; import NextAuth from "next-auth/next"; import CredentialsProvider from "next-auth/providers/credentials"; import GoogleProvider from "next-auth/providers/google"; import bcrypt from "bcrypt" const authOptions = { adapter: MongoDBAdapter(clientPromise), pages: { // signIn: "/auth/login" }, providers: [ GoogleProvider({ clientId: process.env.GOOGLE_CLIENT_ID, clientSecret: process.env.GOOGLE_CLIENT_SECRET, }), ], session: { strategy: "jwt", maxAge: 7 * 24 * 60 * 60, // in seconds }, secret: process.env.NEXTAUTH_SECRET, debug: process.env.NODE_ENV === "development", // database: "", callbacks: { async session({ session, token, user }) { session.user = token.user; return session; }, async jwt({ token, user }) { if (user) { token.user = user; } return token; }, async signin({ profile, account }) { console.log("SIGNING IN+++++++++++++++++++++++++++++++++++++++++++++++++") if (account.provider === "google") { try { await dbConnect(); // connection to database const user = await Users.findOne({ email: profile.email }); //finding if user with the signed email is in users collection if (user && user.name != null && user.image != null) {// if this condition is satisfied then it is first google signup as on the first google signup the name and image field is removed from users collection // Move the 'name' and 'image' fields to UserDetails const userDetails = await UsersDetails.findOne({ user: user._id }); if (!userDetails) { const details = { name: user.name, photo: user.image, user: user._id, }; const newUserDetails = new UsersDetails(details); await newUserDetails.save(); } else { userDetails.name = user.name; userDetails.photo = user.image; await userDetails.save(); } // Update user document to remove 'name' and 'image' fields const updatedUser = await Users.findByIdAndUpdate(user._id, {isSeller:false, emailVerified:true, $unset: { name: 1, image: 1 } }, {new:true}); // Return only 'id' and 'emailVerified' in the session } } catch (error) { console.error("Error while migrating user data:", error); throw error; } } return true; }, }, } const handler = NextAuth(authOptions) export { handler as GET, handler as POST }
请问该方法是否正确?我的代码/方案存在什么问题?
一、signin回调未触发的核心原因
你使用MongoDBAdapter搭配JWT会话策略的组合下,signin回调仅会在用户**首次登录(创建新用户)**时触发一次,后续登录因为JWT复用,不会再进入该回调。如果你的测试账号已经登录过,自然看不到控制台日志。
另外还有两个细节会导致回调逻辑失效:
- 你在
signin里用自定义的Users模型查询,但MongoDBAdapter已经在内部创建了用户,可能和你的模型结构不匹配,导致查询不到目标用户。 - 条件判断
user && user.name != null && user.image != null存在逻辑漏洞:首次登录时MongoDBAdapter已将name和image写入users集合,但如果后续手动修改过集合或模型定义有问题,该条件可能不成立。
二、现有方案的不合理之处
你的思路是在登录流程中嵌入数据迁移,存在两个硬伤:
- 依赖
signin回调的触发时机,而JWT模式下该回调触发频率极低,无法覆盖所有需要迁移的用户(比如已存在的老用户)。 - 数据迁移与登录流程耦合,一旦迁移出错会直接阻断用户登录,影响使用体验。
三、正确的实现方案
1. 改用createUser适配器钩子(推荐)
MongoDBAdapter允许自定义createUser方法,在用户首次创建时直接将name和image写入usersdetails,同时不写入users集合,从根源上避免后续迁移:
const authOptions = { adapter: { ...MongoDBAdapter(clientPromise), async createUser(user) { // 创建不含name、image的基础用户 const baseUser = await Users.create({ email: user.email, emailVerified: user.emailVerified, isSeller: false }); // 同步创建用户详情 await UsersDetails.create({ user: baseUser._id, name: user.name, photo: user.image }); return baseUser; } }, // 其他配置保持不变... }
2. 修复现有逻辑(针对已存在的老用户)
如果需要处理已存在的老用户,不要依赖signin回调,而是在session回调中做一次性迁移,避免阻塞登录流程:
async session({ session, token }) { // 仅在会话初始化时检查并迁移数据 if (token.user?.name || token.user?.image) { await dbConnect(); const user = await Users.findById(token.user.id); if (user && user.name && user.image) { // 新增或更新用户详情 await UsersDetails.findOneAndUpdate( { user: user._id }, { name: user.name, photo: user.image }, { upsert: true } ); // 移除users集合中的name、image字段 await Users.findByIdAndUpdate(user._id, { $unset: { name: 1, image: 1 } }); // 更新token,避免重复执行迁移 token.user = { ...token.user, name: undefined, image: undefined }; } } // 从usersdetails中获取信息并添加到session const userDetails = await UsersDetails.findOne({ user: token.user.id }); if (userDetails) { session.user.name = userDetails.name; session.user.image = userDetails.photo; } return session; }
3. 模型关联的注意事项
确保UsersDetails模型中user字段是正确的MongoDB引用类型:
// models/useraccounts/usersDetail.js import mongoose from 'mongoose'; const UsersDetailsSchema = new mongoose.Schema({ user: { type: mongoose.Schema.Types.ObjectId, ref: 'User', // 对应Users模型的集合名 required: true, unique: true }, name: String, photo: String }); export default mongoose.models.UsersDetails || mongoose.model('UsersDetails', UsersDetailsSchema);
内容的提问来源于stack exchange,提问作者Sebak Thapa

