身份验证与访问控制 auth

这是一个用于现代Web应用的身份验证与访问控制解决方案,专为Next.js 15与Supabase技术栈设计。它提供了完整的用户认证流程,包括登录、注册、邮箱验证和OAuth集成。核心功能包括基于角色的访问控制(区分管理员和普通成员)、服务器端路由保护、多租户数据隔离(以家庭为单位),以及安全的会话管理。该方案强调服务器端优先的安全性,使用HTTP-only Cookie,并提供了丰富的工具函数来简化开发。关键词:Next.js 15, Supabase, 身份验证, 访问控制, 多租户, 角色管理, 服务器组件, 路由保护, 数据隔离。

前端开发 0 次安装 0 次浏览 更新于 3/2/2026

name: auth description: 用于 Next.js 15 + Supabase 应用程序的身份验证和访问控制技能。在实现用户身份验证、保护路由、管理用户会话、强制执行基于角色的访问控制(管理员/成员)或处理基于多租户家庭的数据隔离时使用。涵盖登录/注销、带邮箱验证的注册、OAuth(GitHub)、服务器组件和服务器操作的路由保护、仅管理员功能以及多租户数据访问模式。

身份验证与访问控制

本技能为使用 Next.js 15 + Supabase 的应用程序提供了实现身份验证和访问控制的工作流,采用基于 httpOnly cookie 的服务器端身份验证、混合路由保护和基于多租户家庭的数据隔离。

系统概述

  • 身份验证提供方: 使用 httpOnly cookie 的 Supabase Auth
  • 架构: 使用服务器组件和服务器操作的 Next.js 15 App Router
  • 路由保护: 混合方法(页面级身份验证检查,非仅中间件)
  • 多租户: 基于家庭的数据隔离,使用 RLS 策略
  • 角色: 管理员(家庭中的第一个用户)和成员

核心工作流

保护新路由

保护路由免受未认证用户访问:

  1. @/lib/auth/server-auth 导入 requireAuthRedirect
  2. 在组件开头调用 await requireAuthRedirect()
  3. 如果用户未认证,将被重定向到 /login
import { requireAuthRedirect } from '@/lib/auth/server-auth';

export default async function ProtectedPage() {
  await requireAuthRedirect();

  // 此处保证用户已认证
  return <YourContent />;
}

要保护整个路由组,请将此添加到布局组件中。所有子路由将继承此保护。

保护服务器操作

在服务器操作中要求身份验证:

  1. @/lib/auth/server-auth 导入 requireAuth
  2. 在操作开头调用 const user = await requireAuth()
  3. 如果用户未认证,操作将抛出 UnauthorizedError
'use server';
import { requireAuth } from '@/lib/auth/server-auth';

export async function myAction() {
  const user = await requireAuth();

  // 继续执行已认证的操作
}

获取当前用户数据

  • getCurrentUser() - 身份验证用户(邮箱、ID),如果未登录则返回 null
  • getUserData() - 扩展的个人资料(角色、familyId、名字、姓氏、活跃状态)
  • getCurrentFamilyId() - 仅家庭 ID

全部使用 React cache() - 在同一请求中的多次调用返回缓存值。

实现仅管理员功能

将页面限制为管理员:

import { requireAdminRedirect } from '@/lib/auth/server-auth';

export default async function AdminPage() {
  await requireAdminRedirect();

  // 保证用户是管理员
  return <AdminPanel />;
}

将服务器操作限制为管理员:

'use server';
import { requireAdmin } from '@/lib/auth/server-auth';

export async function adminAction() {
  await requireAdmin(); // 如果不是管理员则抛出错误
  // 继续
}

有条件地显示管理员界面:

import { isAdmin } from '@/lib/auth/server-auth';

export default async function Page() {
  const userIsAdmin = await isAdmin();

  return (
    <>
      <RegularContent />
      {userIsAdmin && <AdminControls />}
    </>
  );
}

强制执行多租户数据访问

确保用户仅访问其自己家庭的数据:

'use server';
import { requireFamilyAccess } from '@/lib/auth/server-auth';

export async function updateFamilyData(familyId: string, data: any) {
  await requireFamilyAccess(familyId); // 如果用户不属于此家庭则抛出错误

  // 保证用户属于此家庭
  await updateDatabase(familyId, data);
}

当获取当前用户的家庭数据时,使用 getCurrentFamilyId() 代替 - 无需 requireFamilyAccess,因为这是他们自己的家庭。

添加新的身份验证页面

创建新的身份验证页面(登录、注册、密码重置):

  1. src/app/(auth)/page-name/ 下创建页面
  2. (auth) 分组布局会自动将已认证用户重定向到 /dashboard
  3. actions.ts 文件中创建相应的服务器操作
  4. 导入并使用 Supabase 客户端:const supabase = await createClient()

(auth) 分组中的页面会自动受到保护,防止已认证用户访问 - 如果已登录,他们将被重定向到仪表板。

实现登录/注销

使用 supabase.auth.signInWithPassword() 进行登录,使用 supabase.auth.signOut() 进行注销。有关完整代码示例,请参阅 references/patterns.md

添加 OAuth 提供方

  1. 在 Supabase 仪表板中启用提供方
  2. 使用 supabase.auth.signInWithOAuth({ provider: 'github', options: {...} })
  3. 回调由 src/app/auth/callback/route.ts 自动处理

有关完整实现示例,请参阅 references/patterns.md

安全要求

令牌验证

  • 始终使用 supabase.auth.getUser() 验证令牌(与服务器重新验证)
  • 切勿在服务器代码中使用 supabase.auth.getSession()(可能被伪造)

仅限服务器端

  • src/lib/auth/server-auth.ts 中的所有身份验证助手都是仅服务器端的
  • 切勿在客户端组件中导入这些内容
  • 切勿向 server-auth.ts 添加 'use server' 指令(会破坏类导出)

中间件

  • 中间件在每次请求时自动刷新令牌
  • 使用 src/lib/supabase/middleware.ts 中的 updateSession()
  • 调用 getUser() 以重新验证令牌
  • 无需额外的令牌刷新逻辑

参考文件

有关详细信息,请参阅:

  • references/file-tree.md - 完整的文件结构和组织
  • references/security.md - 安全最佳实践和要求
  • references/patterns.md - 代码示例和常见模式
  • references/flows.md - 身份验证流程图

快速参考

关键文件: src/lib/auth/server-auth.ts(助手)、src/middleware.ts(令牌刷新)、src/app/dashboard/layout.tsx(仪表板保护)

环境变量: NEXT_PUBLIC_SUPABASE_URLNEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEYNEXT_PUBLIC_SITE_URL

数据库: auth.users(Supabase 身份验证)、public.families(家庭)、public.users(个人资料)

常见问题

‘use server’ 导出错误: 从 server-auth.ts 中删除 'use server' - 这些是实用程序,不是操作

中间件重定向失败: 在服务器操作中使用 requireAuth() - 中间件重定向不适用于 POST 请求

多租户访问被拒绝: 使用 requireFamilyAccess(familyId) 验证家庭所有权