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

Next.js集成NextAuth+Firebase实现角色认证遇错求助

错误原因分析
  1. CredentialsProvider 错误配置 profile 方法:
    CredentialsProvider 是基于凭证的登录方案,不属于 OAuth/OpenID 类提供商,它的配置项里不存在 profile 字段,你复制的是 OAuth 提供商的代码片段,这会直接导致配置不合法报错。

  2. Firebase User 对象无 role 属性:
    signInWithEmailAndPassword 返回的 userCredential.user 是 Firebase 原生 User 对象,仅包含邮箱、UID、验证状态等基础信息,没有预设的 role 字段,后续回调里读取 user.role 会得到 undefined,无法正确设置角色。

  3. authorize 方法错误处理缺陷:
    代码里重复写了两次 .catch 块,且 catch 仅打印错误但未返回 null,会导致 Promise 处于 resolved 状态但返回值为 undefined,不符合 NextAuth 要求(authorize 必须返回用户对象或 null)。

  4. TypeScript 类型未扩展:
    NextAuth 默认的 Session 和 JWT 类型不包含 role 属性,直接赋值会触发 TypeScript 类型校验错误。


正确实现方案

步骤1:移除无效的 profile 配置

直接删除 CredentialsProvider 中的 profile 方法,该配置对凭证登录完全无效。

步骤2:从 Firebase 获取用户角色

提前将用户角色存储在 Firestore/Realtime Database 中,登录成功后根据用户 UID 查询角色,修改 authorize 方法:

// 导入Firestore依赖(用Firestore存角色为例)
import { getFirestore, doc, getDoc } from "firebase/firestore";
const db = getFirestore();

async authorize(credentials): Promise<any> {
  try {
    if (!credentials?.email || !credentials?.password) return null;

    const userCredential = await signInWithEmailAndPassword(
      auth,
      credentials.email,
      credentials.password
    );

    if (userCredential.user) {
      // 根据UID查询用户角色
      const userDoc = await getDoc(doc(db, "users", userCredential.user.uid));
      const userData = userDoc.data();
      // 返回包含角色的用户对象
      return {
        id: userCredential.user.uid,
        email: userCredential.user.email,
        role: userData?.role ?? "user" // 无角色时默认设为user
      };
    }
    return null;
  } catch (error) {
    console.error("登录失败:", error);
    return null; // 必须返回null告知NextAuth登录失败
  }
}

步骤3:扩展 TypeScript 类型并修正回调

在项目根目录创建 types/next-auth.d.ts 文件,扩展 NextAuth 的默认类型:

import NextAuth from "next-auth";

declare module "next-auth" {
  interface User {
    role?: string;
  }

  interface Session {
    user: {
      role?: string;
    } & DefaultSession["user"];
  }

  interface JWT {
    role?: string;
  }
}

然后修正回调逻辑,确保角色正确同步:

callbacks: {
  async jwt({ token, user }) {
    // 登录时将用户角色写入token
    if (user) token.role = user.role;
    return token;
  },
  async session({ session, token }) {
    // 将token中的角色同步到session
    if (session.user) session.user.role = token.role;
    return session;
  },
},

步骤4:完整修正代码

import NextAuth from "next-auth"
import CredentialsProvider from "next-auth/providers/credentials";
import { signInWithEmailAndPassword } from 'firebase/auth';
import { getFirestore, doc, getDoc } from "firebase/firestore";
import auth from '@/app/lib/auth';

const db = getFirestore();

export const authOptions = {
  secret: process.env.AUTH_SECRET,
  pages: {
    signIn: '/signin'
  },
  session: {
    strategy: "jwt" as const,
    maxAge: 3600,
  },

  providers: [
    CredentialsProvider({
      name: 'Credentials',
      credentials: {
        email: { label: "Email", type: "email" },
        password: { label: "Password", type: "password" }
      },
      async authorize(credentials): Promise<any> {
        try {
          if (!credentials?.email || !credentials?.password) return null;

          const userCredential = await signInWithEmailAndPassword(
            auth,
            credentials.email,
            credentials.password
          );

          if (userCredential.user) {
            const userDoc = await getDoc(doc(db, "users", userCredential.user.uid));
            return {
              id: userCredential.user.uid,
              email: userCredential.user.email,
              role: userDoc.data()?.role ?? "user"
            };
          }
          return null;
        } catch (error) {
          console.error("登录失败:", error);
          return null;
        }
      }
    })
  ],

  callbacks: {
    async jwt({ token, user }) {
      if (user) token.role = user.role;
      return token;
    },
    async session({ session, token }) {
      if (session.user) session.user.role = token.role;
      return session;
    },
  },
}

const handler = NextAuth(authOptions)
export { handler as GET, handler as POST}

额外注意事项

  • 配置 Firestore 规则,允许已登录用户查询自己的文档:
    rules_version = '2';
    service cloud.firestore {
      match /databases/{database}/documents {
        match /users/{userId} {
          allow read: if request.auth != null && request.auth.uid == userId;
        }
      }
    }
    
  • 注册用户时需将角色写入 Firestore 的 users/{uid} 文档,比如新用户默认设为 role: "user",管理员角色手动配置。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.28 15:07:04