如何在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
相关产品推荐
相关产品推荐

