supabase-enterprise-rbac について
ロールベースおよびマルチテナントの Supabase アプリを構築するためのスキルです。JWT のロール情報を使い、RLS ポリシーと API の両方でアクセスを制御します。
主な機能
- 01app_metadata によるロール設定
- 02ロール階層に基づく RLS ポリシー
- 03組織単位のアクセス範囲設定
- 04API ミドルウェアでのロール確認
活用シーン
- 01管理者、編集者、閲覧者の権限実装
- 02組織ごとのプロジェクトデータ分離
- 03API ルートと管理操作の保護
インストール
コマンドをコピーしてターミナルで実行します。
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 では、ユーザーの JWT の app_metadata にロール情報を保存し、RLS ポリシーで auth.jwt() ->> 'role' を介してこれらのクレームを読み取ることで、カスタムのロールベースアクセス制御(RBAC)をサポートしています。このスキルでは、app_metadata でのロール管理、ロール階層を適用する RLS ポリシー、組織スコープのアクセス、Admin API によるロール管理、ロールチェックで保護された API エンドポイントを備えた完全な RBAC システムを、実際の @supabase/supabase-js の createClient を使って実装します。
使用する場面: 複数のロール(admin/editor/viewer)を持つアプリケーションの構築、組織スコープのアクセスの実装、Supabase 組み込みの anon/authenticated ロールを超えるカスタム権限システムの作成、またはユーザーロールに基づく API 操作のスコープ設定。
前提条件
- 管理操作用のサービスロールキーを備えた
@supabase/supabase-jsv2+ - JWT クレームと Supabase の
auth.jwt()SQL 関数に関する理解 - RLS ポリシーを作成するための SQL Editor または
psqlによるデータベースアクセス - 認証が設定された Supabase プロジェクト
手順
ワークフローは 3 つのステップで構成されます。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 ポリシー
auth.jwt() -> 'app_metadata' ->> 'role' と ... ->> 'org_id' を読み取る Row Level Security ポリシーを記述します。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() によるロールチェックを組み合わせます。2 つのヘルパー関数、projects/documents/team_members にわたるポリシー一式、インデックス付きの organizations/team_members/projects テーブルスキーマについては、RLS ポリシーと組織スコープのスキーマを参照してください。
ステップ 3:API キーのスコープ設定とアプリケーションコードでのロール適用
RLS だけに依存して API ルートを保護することがないよう、アプリケーション層でもロールを再度適用します。サーバーサイドの withRole() ミドルウェア、リクエストごとのクライアント作成、管理パネルの操作(メンバー一覧表示、ユーザー招待、ロール変更)、組織管理のパターンについては、API キーのスコープ設定とロールの適用を参照してください。
出力
このスキルを完了すると、次のものが生成されます。
- app_metadata によるロール割り当て —
admin.updateUserById()でユーザーの JWT にロールクレームを設定 - JWT クレームの抽出 —
get_user_role()とget_user_org_id()SQL ヘルパー関数 - ロールベースの RLS ポリシー — ロール階層(admin > editor > member > viewer)に基づいてスコープを設定した SELECT/INSERT/UPDATE/DELETE
- 組織スコープのアクセス — JWT クレーム内の
org_idと RLS ポリシーによるマルチテナント分離 - アプリケーション層での適用 — 適切な 401/403 レスポンスを返す、API ルート用の
withRole()ミドルウェア - 管理パネルの操作 — データベースと JWT の両方を更新して、メンバー一覧表示、ユーザー招待、ロール変更を実行
- ロール階層のチェック — ロールの昇格比較に対応する
hasRole()関数
エラー処理
| エラー | 原因 | 解決策 |
|---|---|---|
JWT の app_metadata.role が null | ロールが設定されていない、またはユーザーが再ログインする必要があります | admin.updateUserById() を呼び出してロールを設定します。ユーザーはセッションを更新する必要があります |
| RLS ポリシーが空の結果を返す | JWT クレームがポリシーの条件に一致しません | SQL Editor で auth.jwt() の出力を確認し、app_metadata が正しく設定されていることを検証します |
permission denied for function | ヘルパー関数が作成されていないか、スキーマが誤っています | SECURITY DEFINER を指定して、public スキーマに 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を参照してください