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

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;
    },
  },
};

问题原因与解决方法

核心问题

  1. 邮箱密码登录触发signIn回调时,profile参数为undefined——CredentialsProvider不会返回OAuth风格的profile对象,直接访问profile.email会报错
  2. connectDB调用方式错误,应该执行函数await connectDB()而非直接await变量
  3. 姓名分割逻辑错误,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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.17 06:55:24