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

如何在Next.js中间件中获取Firebase认证用户并校验管理员身份?

在Next.js中间件中获取Firebase认证用户的解决方案

你遇到的核心问题是:客户端Firebase Auth SDK依赖浏览器环境的存储(如localStorage)维护用户状态,但Next.js中间件运行在Edge Runtime,无法直接访问客户端存储,也不能用客户端的onAuthStateChanged方法监听用户状态。前端能正常获取用户是因为代码运行在浏览器里,能读取到用户凭证,但中间件是服务端/边缘环境,得换一种方式获取用户。

正确的实现思路

通过Firebase的会话Cookie机制,在客户端登录后将用户凭证存在Cookie中,然后在中间件里从请求Cookie中提取凭证,用Firebase Admin SDK验证并获取用户信息,再校验管理员权限。

步骤1:客户端登录后设置会话Cookie

用户登录成功后,获取ID令牌并发送到API路由,由后端生成会话Cookie并设置到响应中:

// 前端登录逻辑示例
import { getAuth, signInWithEmailAndPassword } from "firebase/auth";

const login = async (email: string, password: string) => {
  const auth = getAuth();
  const userCredential = await signInWithEmailAndPassword(auth, email, password);
  const idToken = await userCredential.user.getIdToken();

  // 调用API路由设置会话Cookie
  await fetch("/api/set-session-cookie", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ idToken }),
  });
};
// app/api/set-session-cookie/route.ts
import { NextResponse } from "next/server";
import { initializeApp, cert } from "firebase-admin/app";
import { getAuth } from "firebase-admin/auth";

// 初始化Firebase Admin(Edge环境下要确保初始化只执行一次)
const adminApp = initializeApp({
  credential: cert(process.env.FIREBASE_ADMIN_SERVICE_ACCOUNT as string),
});

export async function POST(request: Request) {
  const { idToken } = await request.json();
  const expiresIn = 60 * 60 * 24 * 5 * 1000; // 5天有效期

  const sessionCookie = await getAuth(adminApp).createSessionCookie(idToken, { expiresIn });
  const response = NextResponse.json({ status: "success" });
  
  // 设置Cookie,确保跨域/同域可用,HttpOnly防止XSS
  response.cookies.set("__session", sessionCookie, {
    httpOnly: true,
    secure: process.env.NODE_ENV === "production",
    sameSite: "strict",
    maxAge: expiresIn,
    path: "/",
  });

  return response;
}

步骤2:中间件中验证会话Cookie并校验管理员权限

在middleware.ts中,从请求Cookie提取__session,用Admin SDK验证令牌,获取用户后检查管理员声明:

// src/middleware.ts
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
import { initializeApp, cert } from "firebase-admin/app";
import { getAuth } from "firebase-admin/auth";

// 初始化Admin实例(Edge环境下用lazy初始化避免重复执行)
let adminAuth: ReturnType<typeof getAuth>;
const getAdminAuth = () => {
  if (!adminAuth) {
    const app = initializeApp({
      credential: cert(process.env.FIREBASE_ADMIN_SERVICE_ACCOUNT as string),
    });
    adminAuth = getAuth(app);
  }
  return adminAuth;
};

export async function middleware(request: NextRequest) {
  // 只拦截/admin路由
  if (request.nextUrl.pathname.startsWith("/admin")) {
    const sessionCookie = request.cookies.get("__session")?.value;
    if (!sessionCookie) {
      // 没有Cookie,重定向到登录页
      return NextResponse.redirect(new URL("/login", request.url));
    }

    try {
      // 验证会话Cookie,获取用户信息
      const decodedClaims = await getAdminAuth().verifySessionCookie(sessionCookie, true);
      
      // 检查用户是否有管理员自定义声明(假设你在Firebase控制台给用户设置了admin: true的声明)
      if (!decodedClaims.admin) {
        return NextResponse.redirect(new URL("/", request.url));
      }

      // 验证通过,允许访问
      return NextResponse.next();
    } catch (error) {
      // Cookie无效或过期,重定向到登录页
      return NextResponse.redirect(new URL("/login", request.url));
    }
  }

  // 其他路由直接放行
  return NextResponse.next();
}

// 配置中间件匹配的路由
export const config = {
  matcher: "/admin/:path*",
};

关键注意事项

  • Firebase Admin初始化:在Edge Runtime中,要确保Admin实例只初始化一次,避免重复创建导致资源浪费。
  • 自定义声明设置:需要在Firebase控制台或通过Admin SDK给管理员用户添加admin: true的自定义声明,这样才能在decodedClaims中拿到该字段。
  • Cookie安全性:设置httpOnly、secure(生产环境)、sameSite: strict,防止XSS和CSRF攻击。

内容的提问来源于stack exchange,提问作者Kyle Corbin Hurst

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.09 00:10:21