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

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*'],
};

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.10 10:53:22