Next.js 13 App目录下借助Firebase Auth实现中间件鉴权方案问询
Next.js 13 App目录 + Firebase Auth 身份验证方案
一、可以使用Next.js 13 Middleware实现认证检查,具体步骤如下:
1. 配置Firebase Admin SDK
Middleware运行在服务器端,需要用Firebase Admin SDK验证ID Token。首先安装依赖:
npm install firebase-admin
然后创建lib/firebase-admin.ts初始化Admin SDK:
import { initializeApp, getApps, App, getApp } from 'firebase-admin/app'; import { getAuth } from 'firebase-admin/auth'; import admin from 'firebase-admin'; let app: App; if (!getApps().length) { app = initializeApp({ credential: admin.credential.cert({ projectId: process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID, clientEmail: process.env.FIREBASE_CLIENT_EMAIL, privateKey: process.env.FIREBASE_PRIVATE_KEY?.replace(/\\n/g, '\n'), }), }); } else { app = getApp(); } export const adminAuth = getAuth(app);
注意:需要在环境变量中配置NEXT_PUBLIC_FIREBASE_PROJECT_ID、FIREBASE_CLIENT_EMAIL、FIREBASE_PRIVATE_KEY(从Firebase控制台下载的服务账号密钥中获取)。
2. 编写Middleware文件
在项目根目录创建middleware.ts,拦截指定路由并验证身份:
import { NextResponse } from 'next/server'; import type { NextRequest } from 'next/server'; import { adminAuth } from './lib/firebase-admin'; export async function middleware(request: NextRequest) { // 从HttpOnly Cookie获取Firebase ID Token const idToken = request.cookies.get('firebase-token')?.value; if (!idToken) { // 未认证,重定向到登录页 return NextResponse.redirect(new URL('/login', request.url)); } try { // 验证Token有效性 await adminAuth.verifyIdToken(idToken); // 认证通过,放行请求 return NextResponse.next(); } catch (error) { // Token无效/过期,重定向到登录页 return NextResponse.redirect(new URL('/login', request.url)); } } // 指定需要保护的路由(支持通配符) export const config = { matcher: ['/dashboard/:path*', '/profile/:path*'], };
3. 客户端登录后设置HttpOnly Cookie
Firebase客户端SDK登录后,需要将ID Token存入HttpOnly Cookie(避免XSS风险)。创建登录组件(仅登录页需用"use-client"):
'use client'; import { useState } from 'react'; import { signInWithEmailAndPassword, getAuth } from 'firebase/auth'; import { firebaseApp } from '../lib/firebase-client'; // 客户端Firebase初始化文件 const auth = getAuth(firebaseApp); export default function LoginPage() { const [email, setEmail] = useState(''); const [password, setPassword] = useState(''); const handleLogin = async (e: React.FormEvent) => { e.preventDefault(); try { const userCredential = await signInWithEmailAndPassword(auth, email, password); const idToken = await userCredential.user.getIdToken(); // 调用API路由设置Cookie await fetch('/api/set-token', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ token: idToken }), }); // 重定向到受保护页面 window.location.href = '/dashboard'; } catch (error) { console.error('登录失败:', error); } }; return ( <form onSubmit={handleLogin}> <input type="email" value={email} onChange={(e) => setEmail(e.target.value)} placeholder="邮箱" required /> <input type="password" value={password} onChange={(e) => setPassword(e.target.value)} placeholder="密码" required /> <button type="submit">登录</button> </form> ); }
4. 创建API路由存储Cookie
创建app/api/set-token/route.ts:
import { NextResponse } from 'next/server'; import type { NextRequest } from 'next/server'; export async function POST(request: NextRequest) { const { token } = await request.json(); const response = NextResponse.json({ success: true }); // 设置HttpOnly、Secure(生产环境)Cookie,有效期7天 response.cookies.set('firebase-token', token, { httpOnly: true, secure: process.env.NODE_ENV === 'production', maxAge: 60 * 60 * 24 * 7, path: '/', }); return response; }
二、若不使用Middleware,服务器组件直接验证的最优方案
App目录的服务器组件默认在服务器端运行,无需"use-client",性能更优,适合灵活获取用户数据的场景:
1. 复用Firebase Admin SDK配置
直接使用前面创建的lib/firebase-admin.ts。
2. 在服务器组件中实现认证逻辑
创建受保护页面app/dashboard/page.tsx:
import { adminAuth } from '../lib/firebase-admin'; import { cookies } from 'next/headers'; import { redirect } from 'next/navigation'; export default async function DashboardPage() { const cookieStore = cookies(); const idToken = cookieStore.get('firebase-token')?.value; if (!idToken) { redirect('/login'); } try { // 验证Token并获取用户信息 const decodedToken = await adminAuth.verifyIdToken(idToken); const user = await adminAuth.getUser(decodedToken.uid); return ( <div> <h1>欢迎回来,{user.displayName || '用户'}</h1> <p>你的邮箱:{user.email}</p> </div> ); } catch (error) { // Token无效,重定向登录 redirect('/login'); } }
3. 复用认证逻辑(可选)
创建工具函数lib/with-auth.ts统一处理认证:
import { adminAuth } from './firebase-admin'; import { cookies } from 'next/headers'; import { redirect } from 'next/navigation'; export async function requireAuth() { const cookieStore = cookies(); const idToken = cookieStore.get('firebase-token')?.value; if (!idToken) { redirect('/login'); } try { const decodedToken = await adminAuth.verifyIdToken(idToken); return decodedToken; } catch (error) { redirect('/login'); } }
之后在服务器组件中复用:
import { requireAuth } from '../lib/with-auth'; import { adminAuth } from '../lib/firebase-admin'; export default async function ProfilePage() { const decodedToken = await requireAuth(); const user = await adminAuth.getUser(decodedToken.uid); return ( <div> <h2>个人资料</h2> <p>UID:{user.uid}</p> <p>创建时间:{user.metadata.creationTime}</p> </div> ); }
方案对比
- Middleware方案:适合批量保护多个路由,无需在每个页面重复写认证逻辑,维护成本低。
- 服务器组件方案:更灵活,可直接在组件内获取用户数据并渲染,适合需要根据用户信息动态展示内容的场景,性能更优。
内容的提问来源于stack exchange,提问作者Huzefa zeenwala
相关产品推荐
相关产品推荐

