如何在Next.js中间件中使用Firebase User实现路由保护并解决useContext报错
问题原因分析
你遇到的错误是因为Next.js 中间件的运行环境不支持 React Context:
- React 的
useContext只能在 React 函数组件或自定义 Hooks 内部调用,而中间件运行在 Node.js/边缘 Runtime 中,不属于 React 组件树环境,直接访问 Context 会触发Cannot read properties of null (reading 'useContext')错误。 - 你在页面组件中能正常获取 User 对象,是因为页面属于 React 组件树,符合 Context 的使用场景。
要在中间件中实现登录状态校验,需要将 Firebase 的认证信息存储到 HttpOnly Cookie 中,然后在中间件里通过 Firebase Admin SDK 验证 Cookie 有效性,以此判断用户登录状态。
步骤1:前端登录后将 Token 存入 Cookie
在你的登录逻辑(比如 Context 的 login 函数)中,获取 Firebase ID Token 并调用 API 路由将其转为 Session Cookie:
// 示例:Context 中的登录函数 import { signInWithEmailAndPassword, getAuth } from "firebase/auth"; const login = async (email, password) => { const auth = getAuth(); const userCredential = await signInWithEmailAndPassword(auth, email, password); const idToken = await userCredential.user.getIdToken(); // 调用 API 路由存储 Session Cookie await fetch("/api/set-session-cookie", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ idToken }), }); };
步骤2:创建 API 路由生成 Session Cookie
在 pages/api/set-session-cookie.js 中,用 Firebase Admin SDK 将 ID Token 转为 Session Cookie:
import { initializeApp, cert } from "firebase-admin/app"; import { getAuth } from "firebase-admin/auth"; // 初始化 Firebase Admin(提前下载服务账号密钥放在项目根目录) if (!initializeApp.length) { initializeApp({ credential: cert("./service-account-key.json"), }); } const adminAuth = getAuth(); export default async function handler(req, res) { const { idToken } = req.body; const expiresIn = 60 * 60 * 24 * 5 * 1000; // Cookie 有效期5天 const sessionCookie = await adminAuth.createSessionCookie(idToken, { expiresIn }); // 设置 HttpOnly、Secure Cookie(生产环境开启 Secure) res.setHeader( "Set-Cookie", `session=${sessionCookie}; HttpOnly; Path=/; Max-Age=${expiresIn / 1000}; ${ process.env.NODE_ENV === "production" ? "Secure;" : "" }` ); res.status(200).json({ success: true }); }
步骤3:中间件中验证 Cookie 并重定向
修改 middleware.js,读取并验证 Session Cookie,根据状态处理重定向:
import { NextResponse } from "next/server"; import { getAuth } from "firebase-admin/auth"; import { initializeApp, cert } from "firebase-admin/app"; // 初始化 Firebase Admin if (!initializeApp.length) { initializeApp({ credential: cert("./service-account-key.json"), }); } const adminAuth = getAuth(); export async function middleware(req) { const sessionCookie = req.cookies.get("session")?.value; let isLoggedIn = false; if (sessionCookie) { try { // 验证 Session Cookie 有效性 await adminAuth.verifySessionCookie(sessionCookie, true); isLoggedIn = true; } catch (err) { // Cookie 无效,清除并跳转到登录页 return NextResponse.redirect(new URL("/login", req.url)).clearCookie("session"); } } // 根据登录状态处理重定向 const isLoginPage = req.nextUrl.pathname === "/login"; const isProtectedRoute = req.nextUrl.pathname.startsWith("/dashboard"); if (!isLoggedIn && isProtectedRoute) { return NextResponse.redirect(new URL("/login", req.url)); } if (isLoggedIn && isLoginPage) { return NextResponse.redirect(new URL("/dashboard", req.url)); } return NextResponse.next(); } // 指定中间件生效的路由 export const config = { matcher: ["/", "/login", "/dashboard/:path*"], };
内容的提问来源于stack exchange,提问作者Sky Lurk
相关产品推荐
相关产品推荐

