关于 supabase-enterprise-rbac
面向构建多角色和多租户 Supabase 应用的技能,提供从 JWT 角色声明到数据库与 API 层校验的 RBAC 实现流程。
主要功能
- 01通过 app_metadata 分配角色
- 02基于角色层级编写 RLS 策略
- 03按组织隔离数据访问
- 04使用 API 中间件校验角色
适用场景
- 01实现管理员、编辑者和查看者权限
- 02隔离不同组织的项目数据
- 03保护 API 路由和管理操作
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ jeremylongshore/ tons-of-skills-marketplace --skill supabase-enterprise-rbac安装后在 Claude Code 中输入/supabase-enterprise-rbac确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/api-scoping-and-enforcement.mderrors.mdexamples.mdrls-policies.mdrole-assignment.mdrole-implementation.mdsso-integration.md详细介绍
Supabase 企业级 RBAC
概述
Supabase 支持自定义基于角色的访问控制(RBAC):将角色信息存储在用户 JWT 的 app_metadata 中,然后在 RLS 策略中通过 auth.jwt() ->> 'role' 读取这些声明。本技能实现了一套完整的 RBAC 系统:在 app_metadata 中存储角色、通过 RLS 策略强制执行角色层级、按组织范围限制访问、通过 Admin API 管理角色,以及使用角色检查保护 API 端点——所有操作均使用来自 @supabase/supabase-js 的真实 createClient。
适用场景: 构建包含多种角色(管理员/编辑者/查看者)的应用、实现组织范围内的访问控制、创建超出 Supabase 内置 anon/authenticated 角色能力范围的自定义权限系统,或按用户角色限制 API 操作范围。
前置条件
@supabase/supabase-jsv2+,以及用于管理操作的服务角色密钥- 了解 JWT 声明和 Supabase 的
auth.jwt()SQL 函数 - 可通过 SQL Editor 或
psql访问数据库,以创建 RLS 策略 - 已配置身份验证的 Supabase 项目
使用说明
工作流分为三个步骤:将角色分配到 JWT 中、通过 RLS 在数据库中强制执行角色权限,然后在应用代码中再次强制执行。
第 1 步:通过 app_metadata 和 JWT 声明定义角色
使用 Admin API 将自定义角色存储在用户的 app_metadata 中。这些声明会出现在用户收到的每个 JWT 中,并且可在 RLS 策略中读取。使用服务角色客户端分配角色:
// Define the role hierarchy
type AppRole = 'admin' | 'editor' | 'viewer' | 'member';
// Assign a role to a user (admin operation, service role key required)
async function setUserRole(userId: string, role: AppRole, orgId: string) {
const { data, error } = await supabase.auth.admin.updateUserById(userId, {
app_metadata: { role, org_id: orgId },
});
if (error) throw new Error(`Failed to set role: ${error.message}`);
return data.user;
}
在应用代码中使用 supabase.auth.getUser() 读取角色,并将其与数字层级进行比较,以便 hasRole('editor', 'viewer') 返回 true。有关完整的服务角色客户端设置、细粒度权限、批量 assignTeamRoles()、getCurrentUserRole()、getCurrentOrg()、hasRole() 和 requireRole() 守卫,请参阅角色分配和 JWT 提取。
第 2 步:使用 JWT 角色声明编写 RLS 策略
编写行级安全(RLS)策略,以读取 auth.jwt() -> 'app_metadata' ->> 'role' 和 ... ->> 'org_id'。将 JWT 提取逻辑封装在辅助函数中,让每个策略都保持清晰易读:
CREATE OR REPLACE FUNCTION public.get_user_role()
RETURNS text AS $$
SELECT coalesce(auth.jwt() -> 'app_metadata' ->> 'role', 'viewer');
$$ LANGUAGE sql STABLE SECURITY DEFINER;
CREATE POLICY "editors_create_projects" ON public.projects
FOR INSERT WITH CHECK (
org_id = get_user_org_id()
AND get_user_role() IN ('admin', 'editor')
);
每项操作(SELECT/INSERT/UPDATE/DELETE)的策略都会将 org_id = get_user_org_id() 租户检查与 get_user_role() 角色检查结合起来。有关这两个辅助函数、适用于 projects/documents/team_members 的完整策略集,以及包含索引的 organizations/team_members/projects 表结构,请参阅 RLS 策略和组织范围内的架构。
第 3 步:API 密钥范围限制和应用代码中的角色强制执行
在应用层再次强制执行角色权限,确保 API 路由绝不只依赖 RLS。有关服务端 withRole() 中间件、按请求创建客户端、管理面板操作(列出成员、邀请用户、更改角色)以及组织管理模式,请参阅 API 密钥范围限制和角色强制执行。
输出
完成本技能后,将实现以下功能:
- 通过 app_metadata 分配角色 —
admin.updateUserById()在用户 JWT 中设置角色声明 - 提取 JWT 声明 —
get_user_role()和get_user_org_id()SQL 辅助函数 - 基于角色的 RLS 策略 — 按角色层级(管理员 > 编辑者 > 成员 > 查看者)限制 SELECT/INSERT/UPDATE/DELETE 操作
- 组织范围内的访问控制 — 通过 JWT 声明和 RLS 策略中的
org_id实现多租户隔离 - 应用层强制执行 — 用于 API 路由的
withRole()中间件,并正确返回 401/403 响应 - 管理面板操作 — 列出成员、邀请用户、更改角色,同时更新数据库和 JWT
- 角色层级检查 — 支持比较角色权限等级的
hasRole()函数
错误处理
| 错误 | 原因 | 解决方案 |
|---|---|---|
JWT 中的 app_metadata.role 为 null | 未设置角色,或用户需要重新登录 | 调用 admin.updateUserById() 设置角色;用户必须刷新会话 |
| RLS 策略返回空结果 | JWT 声明与策略条件不匹配 | 在 SQL Editor 中检查 auth.jwt() 的输出;确认 app_metadata 设置正确 |
permission denied for function | 未创建辅助函数,或架构不正确 | 在 public 架构中使用 SECURITY DEFINER 创建 get_user_role() |
| 用户角色变更未生效 | JWT 缓存了旧声明 | 用户必须退出登录后重新登录,或调用 supabase.auth.refreshSession() |
duplicate key value violates unique constraint | 用户已在组织中 | 插入前检查 team_members 表中是否已有对应条目 |
team_members 上出现 foreign key violation | 用户或组织不存在 | 插入成员关系前,确认 user_id 和 org_id 均存在 |
| 绕过角色层级 | 使用服务角色直接访问数据库 | 服务角色按设计会绕过 RLS——仅限在服务端管理操作中使用 |
示例
示例 1 — 在组件中快速检查角色:
async function canEditProject(): Promise<boolean> {
const { data: { user } } = await supabase.auth.getUser();
const role = user?.app_metadata?.role;
return role === 'admin' || role === 'editor';
}
示例 2 — 验证 RLS 策略是否正常工作:
-- Test as an editor in org-123
SET request.jwt.claims = '{"sub": "user-uuid", "role": "authenticated", "app_metadata": {"role": "editor", "org_id": "org-123"}}';
SELECT * FROM projects; -- returns only org-123 rows
INSERT INTO projects (org_id, name, created_by) VALUES ('org-123', 'Test', 'user-uuid'); -- succeeds
DELETE FROM projects WHERE id = 'some-project-id'; -- fails (editors cannot delete)
RESET request.jwt.claims;
完整的入门示例(onboardOrganization()——创建组织、将创建者设为管理员、填充 team_members),请参阅角色分配参考和 API 范围限定参考。
资源
- 自定义声明和 RBAC — Supabase 文档
- Auth Admin updateUserById — Supabase 文档
- 行级安全 — Supabase 文档
- auth.jwt() 函数 — Supabase 文档
- 多租户模式 — Supabase 文档
- inviteUserByEmail — Supabase 文档
后续步骤
- 数据库迁移模式请参阅
supabase-migration-deep-dive - 安全加固和 API 密钥范围限定请参阅
supabase-security-basics - 数据处理和 GDPR 合规请参阅
supabase-data-handling