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

NextAuth.js合并Credentials与Google Provider会话数据问题(NextJS)

问题描述

我正在开发一个使用NextAuth v4.19的项目,采用Credentials(数据库)和Google作为登录提供商。使用Credentials提供商时一切正常,能获取包含所有数据库字段的session.user;但通过Google注册/登录时,返回的会话仅包含谷歌返回的数据及令牌信息。我需要将谷歌返回的数据与我的用户数据库字段合并,并传递到客户端的session对象中。

相关代码如下:

export const authOptions = {
  providers: [
    GoogleProvider({
      clientId: process.env.GOOGLE_CLIENT_ID,
      clientSecret: process.env.GOOGLE_CLIENT_SECRET,
    }),
    CredentialsProvider({
      name: "Credentials",
      async authorize(credentials, req) {
        const { username, password } = credentials;
        const user = await User.findOne({ email: username }).exec();

        const isValid = await user.comparePassword(password);

        if (!isValid) {
          return false;
        }

        return user;
      },
    }),
  ],
  callbacks: {
    signIn: async ({ user, account, profile }) => {
      console.log("signIn", user, account, profile);
      if (account.provider === "google") {
        const existingUser = await User.findOne({
          email: user.email,
        }).exec();

        if (existingUser) {
          console.log(
            "Google user already exists, returning existing user",
            existingUser
          );
          return existingUser;
        }

        console.log("New google account, saving in db...");
        const newUser = await User.create({
          name: user.name,
          email: user.email,
          password: "googlepassword", // @TOOD: bcrypt this
          photo: user.image,
          provider: true,
          providerName: "google",
        });

        console.log("Google user saved, returning:", newUser);
        return newUser;
      } else {
        return true;
      }
    },
    // Persist the OAuth access_token and or the user id to the token right after signin
    jwt: async ({ token, user }) => {
      user && (token.user = user);
      return token;
    },
    session: async ({ session, token, user }) => {
      session.user = token.user;
      return session;
    },
  },
  secret: process.env.NEXTAUTH_SECRET,
  pages: {
    signIn: "/auth/signin",
    signOut: "/auth/signout",
    error: "/auth/signin", // Error code passed in query string as ?error=
    verifyRequest: "/auth/verify-request", // (used for check email message)
    newUser: "/auth/new-user", // New users will be directed here on first sign in (leave the property out if not of interest)
  },
};

export default NextAuth(authOptions);

使用Credentials登录时的session.user:

{
  "_id": "640f9ecde855917558d80449",
  "email": "email@gmail.com",
  "password": "$2b$10$Skj418vLiKyc0yM/Wg3mJu9c5ahjn175zALf/XxgCyJzDz9vxCJD6",
  "name": "Name Surname",
  "provider": true,
  "providerName": "google",
  "photo": "https://lh3.googleusercontent.com/a",
  "friendshipRequests": [],
  "friends": [],
  "favorites": [],
  "createdAt": "2023-03-13T22:08:13.596Z",
  "updatedAt": "2023-03-13T22:08:13.596Z",
  "__v": 0
}

使用Google登录时的session.user:

{
  "id": "114524123847825110849",
  "name": "Name Surname",
  "email": "email@gmail.com",
  "image": "https://lh3.googleusercontent.com/a/AGNm..."
}
解决方案

问题出在signIn回调的返回值上——NextAuth的signIn回调对于OAuth提供商,不能直接返回数据库用户对象,它仅接受布尔值或URL字符串来控制登录流程。你之前在Google登录分支返回existingUser或newUser,会被NextAuth当作true处理,导致后续jwt回调拿到的user依然是Google返回的原始数据,而非数据库中的完整用户对象。

修改步骤如下:

  1. 调整signIn回调,完成数据库操作后返回true,不要返回用户对象
  2. 在jwt回调中区分登录提供商,Google登录时主动查询数据库获取完整用户信息

修改后的完整代码:

export const authOptions = {
  providers: [
    GoogleProvider({
      clientId: process.env.GOOGLE_CLIENT_ID,
      clientSecret: process.env.GOOGLE_CLIENT_SECRET,
    }),
    CredentialsProvider({
      name: "Credentials",
      async authorize(credentials, req) {
        const { username, password } = credentials;
        const user = await User.findOne({ email: username }).exec();

        if (!user) {
          return null; // 替换返回false,符合NextAuth规范
        }

        const isValid = await user.comparePassword(password);

        if (!isValid) {
          return null;
        }

        return user;
      },
    }),
  ],
  callbacks: {
    signIn: async ({ user, account }) => {
      if (account.provider === "google") {
        const existingUser = await User.findOne({ email: user.email }).exec();
        // 不存在则创建新用户
        if (!existingUser) {
          await User.create({
            name: user.name,
            email: user.email,
            password: "googlepassword", // 记得后续用bcrypt加密
            photo: user.image,
            provider: true,
            providerName: "google",
          });
        }
        // 始终返回true允许登录
        return true;
      }
      return true;
    },
    jwt: async ({ token, user, account }) => {
      // Credentials登录时直接使用数据库返回的完整用户
      if (account?.provider === "credentials") {
        token.user = user;
      }
      // Google登录时查询数据库获取完整用户
      if (account?.provider === "google") {
        const dbUser = await User.findOne({ email: token.email }).exec();
        token.user = dbUser;
      }
      return token;
    },
    session: async ({ session, token }) => {
      session.user = token.user;
      return session;
    },
  },
  secret: process.env.NEXTAUTH_SECRET,
  pages: {
    signIn: "/auth/signin",
    signOut: "/auth/signout",
    error: "/auth/signin",
    verifyRequest: "/auth/verify-request",
    newUser: "/auth/new-user",
  },
};

export default NextAuth(authOptions);

关键修改说明

  • signIn回调:仅负责用户的创建/校验逻辑,返回true允许登录流程继续,不再返回用户对象
  • jwt回调:区分登录提供商,Google登录时主动从数据库拉取完整用户数据,确保token.user是数据库中的完整对象
  • Credentials授权优化:将返回false改为null,贴合NextAuth的官方规范

修改完成后,Google登录的session.user会和Credentials登录保持一致,包含数据库中的所有字段。

内容的提问来源于stack exchange,提问作者rsilva

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.28 04:17:44