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

使用Next Auth+Firebase Adapter实现邮箱密码登录的问题

解决NextAuth + Firebase Adapter邮箱密码登录问题

你的核心问题是没有配置CredentialsProvider——当前代码里只有Google登录提供者,调用signIn("credentials")时NextAuth找不到对应的认证逻辑,自然不会触发任何回调或输出。以下是完整的修复方案:

步骤1:安装依赖

需要补充Firebase相关依赖:

npm install firebase-admin next-auth@latest

步骤2:初始化Firebase Admin

在项目根目录创建lib/firebase-admin.ts:

import admin from "firebase-admin";

// 避免重复初始化
if (!admin.apps.length) {
  admin.initializeApp({
    credential: admin.credential.cert({
      projectId: process.env.FIREBASE_PROJECT_ID,
      clientEmail: process.env.FIREBASE_CLIENT_EMAIL,
      privateKey: process.env.FIREBASE_PRIVATE_KEY!.replace(/\\n/g, "\n"),
    }),
  });
}

export const adminAuth = admin.auth();

步骤3:修改NextAuth配置文件

替换app/api/auth/[...nextauth]/route.ts的内容:

import NextAuth, { AuthOptions } from "next-auth";
import { FirestoreAdapter } from "@next-auth/firebase-adapter";
import CredentialsProvider from "next-auth/providers/credentials";
import { adminAuth } from "@/lib/firebase-admin";

export const authOptions: AuthOptions = {
  providers: [
    CredentialsProvider({
      name: "邮箱密码登录",
      credentials: {
        email: { label: "邮箱", type: "email", placeholder: "your@email.com" },
        password: { label: "密码", type: "password" },
      },
      async authorize(credentials) {
        if (!credentials?.email || !credentials?.password) return null;

        try {
          // 调用Firebase Auth REST API验证邮箱密码(Admin SDK无法直接验证密码)
          const response = await fetch(
            `https://identitytoolkit.googleapis.com/v1/accounts:signInWithPassword?key=${process.env.NEXT_PUBLIC_FIREBASE_API_KEY}`,
            {
              method: "POST",
              headers: { "Content-Type": "application/json" },
              body: JSON.stringify({
                email: credentials.email,
                password: credentials.password,
                returnSecureToken: true,
              }),
            }
          );

          const data = await response.json();
          if (!response.ok || data.error) throw new Error(data.error?.message || "登录失败");

          // 通过Admin SDK获取完整用户信息
          const userRecord = await adminAuth.getUserByEmail(credentials.email);
          return {
            id: userRecord.uid,
            email: userRecord.email,
            name: userRecord.displayName,
            image: userRecord.photoURL,
          };
        } catch (error) {
          console.error("登录验证失败:", error);
          return null;
        }
      },
    }),
  ],
  adapter: FirestoreAdapter({
    credential: admin.credential.cert({
      projectId: process.env.FIREBASE_PROJECT_ID,
      clientEmail: process.env.FIREBASE_CLIENT_EMAIL,
      privateKey: process.env.FIREBASE_PRIVATE_KEY!.replace(/\\n/g, "\n"),
    }),
  }) as AuthOptions["adapter"],
  callbacks: {
    async signIn({ user }) {
      return !!user; // 用户验证通过则允许登录
    },
    async jwt({ token, user }) {
      // 将用户信息写入JWT
      if (user) {
        token.id = user.id;
        token.email = user.email;
        token.name = user.name;
        token.picture = user.image;
      }
      return token;
    },
    async session({ session, token }) {
      // 将JWT中的信息同步到会话
      if (session.user) {
        session.user.id = token.id as string;
        session.user.email = token.email as string;
        session.user.name = token.name as string;
        session.user.image = token.picture as string;
      }
      return session;
    },
  },
  pages: {
    signIn: "/login",
  },
  session: {
    strategy: "jwt",
  },
  secret: process.env.NEXTAUTH_SECRET, // 必须设置此环境变量
};

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

步骤4:客户端登录页面示例

创建app/login/page.tsx:

"use client";

import { signIn } from "next-auth/react";
import { useState } from "react";

export default function LoginPage() {
  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");
  const [error, setError] = useState("");

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();
    setError("");

    const result = await signIn("credentials", {
      email,
      password,
      redirect: false, // 关闭自动跳转,手动处理结果
    });

    if (result?.error) {
      setError(result.error);
    } else {
      window.location.href = "/"; // 登录成功跳转到首页
    }
  };

  return (
    <div className="max-w-md mx-auto mt-10">
      <h1 className="text-2xl font-bold mb-6">登录</h1>
      <form onSubmit={handleSubmit} className="space-y-4">
        <div>
          <label className="block mb-1">邮箱</label>
          <input
            type="email"
            value={email}
            onChange={(e) => setEmail(e.target.value)}
            className="w-full p-2 border rounded"
            required
          />
        </div>
        <div>
          <label className="block mb-1">密码</label>
          <input
            type="password"
            value={password}
            onChange={(e) => setPassword(e.target.value)}
            className="w-full p-2 border rounded"
            required
          />
        </div>
        {error && <p className="text-red-500">{error}</p>}
        <button
          type="submit"
          className="w-full p-2 bg-blue-500 text-white rounded hover:bg-blue-600"
        >
          登录
        </button>
      </form>
    </div>
  );
}

步骤5:配置环境变量

在.env.local中添加:

NEXTAUTH_SECRET=你的随机密钥(可通过`openssl rand -hex 32`生成)
NEXT_PUBLIC_FIREBASE_API_KEY=你的Firebase项目API Key
FIREBASE_PROJECT_ID=你的Firebase项目ID
FIREBASE_CLIENT_EMAIL=你的Firebase服务账号邮箱
FIREBASE_PRIVATE_KEY=你的Firebase服务账号私钥(注意替换换行符为`\n`)

关键说明

  1. CredentialsProvider是核心:必须配置此提供者才能启用邮箱密码登录
  2. Firebase密码验证限制:Admin SDK无法直接验证用户密码,因此需要调用Firebase官方REST API完成验证
  3. 会话同步:通过jwt和session回调确保用户信息在会话中正确传递
  4. 错误处理:客户端使用redirect: false可以获取登录结果,便于展示错误信息

内容的提问来源于stack exchange,提问作者José Carlos

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.06 17:36:03