Next.js 14服务端组件中获取Firebase Auth ID Token的最佳方案
在Next.js 14(App Router)服务端组件中获取Firebase Auth ID Token的最佳方案
以下是几种适配不同场景的可行方案,按生产环境推荐优先级排序:
方案一:使用Firebase Auth会话Cookie(推荐)
Firebase Auth支持将短期ID Token转换为长期会话Cookie,具备HttpOnly、Secure等安全属性,适合服务端读取验证,是生产环境的最优选择。
步骤1:客户端触发会话Cookie生成
在客户端组件中,用户登录后获取ID Token,调用Next.js API路由生成会话Cookie:
// 客户端组件(如LoginClient.jsx) import { auth } from '@/lib/firebase'; import { signInWithEmailAndPassword } from 'firebase/auth'; const handleLogin = async (email, password) => { const userCredential = await signInWithEmailAndPassword(auth, email, password); const idToken = await userCredential.user.getIdToken(); // 调用API路由生成会话Cookie await fetch('/api/set-session', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ idToken }), }); };
步骤2:API路由生成并设置会话Cookie
在app/api/set-session/route.js中,用Firebase Admin SDK验证ID Token并生成会话Cookie:
import { adminAuth } from '@/lib/firebase-admin'; import { cookies } from 'next/headers'; export async function POST(request) { const { idToken } = await request.json(); const expiresIn = 60 * 60 * 24 * 5 * 1000; // 5天有效期 // 验证ID Token并生成会话Cookie const sessionCookie = await adminAuth.createSessionCookie(idToken, { expiresIn }); // 设置安全Cookie属性 cookies().set('firebase-session', sessionCookie, { httpOnly: true, secure: process.env.NODE_ENV === 'production', sameSite: 'strict', maxAge: expiresIn / 1000, path: '/', }); return new Response(JSON.stringify({ status: 'success' }), { status: 200 }); }
步骤3:服务端组件读取并验证会话Cookie
在服务端组件中读取Cookie,验证后获取用户UID:
// 服务端组件(如DashboardServer.jsx) import { adminAuth } from '@/lib/firebase-admin'; import { cookies } from 'next/headers'; import { redirect } from 'next/navigation'; export default async function Dashboard() { const sessionCookie = cookies().get('firebase-session')?.value; if (!sessionCookie) return redirect('/login'); try { // 验证会话Cookie并解析用户信息 const decodedClaims = await adminAuth.verifySessionCookie(sessionCookie, true); const uid = decodedClaims.uid; // 基于UID获取用户数据或执行其他逻辑 return <div>当前用户UID:{uid}</div>; } catch (error) { // Cookie无效,清除后跳转登录页 cookies().delete('firebase-session'); return redirect('/login'); } }
方案二:客户端主动存储ID Token到Cookie(适合简单场景)
若不需要长期登录状态,可直接将ID Token存入Cookie(注意:ID Token有效期仅1小时,需定时刷新)。
步骤1:客户端监听Auth状态,存储/刷新Token
// 客户端组件(如AuthProvider.jsx) import { auth } from '@/lib/firebase'; import { onAuthStateChanged } from 'firebase/auth'; import { cookies } from 'next/headers'; import { useEffect } from 'react'; export function AuthProvider({ children }) { useEffect(() => { const unsubscribe = onAuthStateChanged(auth, async (user) => { if (user) { // 初始获取Token并存入Cookie let idToken = await user.getIdToken(); cookies().set('firebase-token', idToken, { secure: process.env.NODE_ENV === 'production', sameSite: 'lax', maxAge: 3600, path: '/', }); // 每55分钟强制刷新Token(避免过期) const refreshInterval = setInterval(async () => { idToken = await user.getIdToken(true); cookies().set('firebase-token', idToken, { secure: process.env.NODE_ENV === 'production', sameSite: 'lax', maxAge: 3600, path: '/', }); }, 55 * 60 * 1000); return () => clearInterval(refreshInterval); } else { // 用户登出时清除Cookie cookies().delete('firebase-token'); } }); return () => unsubscribe(); }, []); return children; }
步骤2:服务端组件读取并验证Token
// 服务端组件 import { adminAuth } from '@/lib/firebase-admin'; import { cookies } from 'next/headers'; import { redirect } from 'next/navigation'; export default async function Profile() { const idToken = cookies().get('firebase-token')?.value; if (!idToken) return redirect('/login'); try { const decodedToken = await adminAuth.verifyIdToken(idToken); const uid = decodedToken.uid; return <div>用户UID:{uid}</div>; } catch (error) { cookies().delete('firebase-token'); return redirect('/login'); } }
方案三:Next.js Middleware统一处理验证
通过Middleware提前验证会话Cookie/Token,将UID传递给服务端组件,避免重复验证逻辑。
步骤1:Middleware中验证并传递UID
// middleware.js import { adminAuth } from '@/lib/firebase-admin'; import { NextResponse } from 'next/server'; export async function middleware(request) { const sessionCookie = request.cookies.get('firebase-session')?.value; if (!sessionCookie) { return NextResponse.redirect(new URL('/login', request.url)); } try { const decodedClaims = await adminAuth.verifySessionCookie(sessionCookie, true); // 将UID添加到请求头,供服务端组件读取 const headers = new Headers(request.headers); headers.set('x-firebase-uid', decodedClaims.uid); return NextResponse.next({ headers }); } catch (error) { const response = NextResponse.redirect(new URL('/login', request.url)); response.cookies.delete('firebase-session'); return response; } } // 指定需要验证的路由 export const config = { matcher: ['/dashboard/:path*', '/profile'], };
步骤2:服务端组件读取请求头中的UID
// 服务端组件 import { headers } from 'next/headers'; export default async function Dashboard() { const uid = headers().get('x-firebase-uid'); return <div>当前用户UID:{uid}</div>; }
方案对比
- 方案一:安全级别最高,支持长期登录状态,适合生产环境。
- 方案二:实现简单,但需处理Token刷新逻辑,适合小型项目或测试场景。
- 方案三:统一验证逻辑,减少组件重复代码,适合路由较多的项目。
内容的提问来源于stack exchange,提问作者Rajendra A Verma
相关产品推荐
相关产品推荐

