You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.07 15:17:26