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 策略
- 角色: 管理员(家庭中的第一个用户)和成员
核心工作流
保护新路由
保护路由免受未认证用户访问:
- 从
@/lib/auth/server-auth导入requireAuthRedirect - 在组件开头调用
await requireAuthRedirect() - 如果用户未认证,将被重定向到
/login
import { requireAuthRedirect } from '@/lib/auth/server-auth';
export default async function ProtectedPage() {
await requireAuthRedirect();
// 此处保证用户已认证
return <YourContent />;
}
要保护整个路由组,请将此添加到布局组件中。所有子路由将继承此保护。
保护服务器操作
在服务器操作中要求身份验证:
- 从
@/lib/auth/server-auth导入requireAuth - 在操作开头调用
const user = await requireAuth() - 如果用户未认证,操作将抛出
UnauthorizedError
'use server';
import { requireAuth } from '@/lib/auth/server-auth';
export async function myAction() {
const user = await requireAuth();
// 继续执行已认证的操作
}
获取当前用户数据
getCurrentUser()- 身份验证用户(邮箱、ID),如果未登录则返回 nullgetUserData()- 扩展的个人资料(角色、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,因为这是他们自己的家庭。
添加新的身份验证页面
创建新的身份验证页面(登录、注册、密码重置):
- 在
src/app/(auth)/page-name/下创建页面 (auth)分组布局会自动将已认证用户重定向到/dashboard- 在
actions.ts文件中创建相应的服务器操作 - 导入并使用 Supabase 客户端:
const supabase = await createClient()
(auth) 分组中的页面会自动受到保护,防止已认证用户访问 - 如果已登录,他们将被重定向到仪表板。
实现登录/注销
使用 supabase.auth.signInWithPassword() 进行登录,使用 supabase.auth.signOut() 进行注销。有关完整代码示例,请参阅 references/patterns.md。
添加 OAuth 提供方
- 在 Supabase 仪表板中启用提供方
- 使用
supabase.auth.signInWithOAuth({ provider: 'github', options: {...} }) - 回调由
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_URL、NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY、NEXT_PUBLIC_SITE_URL
数据库: auth.users(Supabase 身份验证)、public.families(家庭)、public.users(个人资料)
常见问题
‘use server’ 导出错误: 从 server-auth.ts 中删除 'use server' - 这些是实用程序,不是操作
中间件重定向失败: 在服务器操作中使用 requireAuth() - 中间件重定向不适用于 POST 请求
多租户访问被拒绝: 使用 requireFamilyAccess(familyId) 验证家庭所有权