RBAC × 例外フラグ × RLS の3レイヤー構成にしたら、権限まわりが一気に“壊れにくく”なった話
最近、チームログ共有アプリの権限まわりを大きく改修しました。
結果として、アプリ全体の一貫性・保守性・安全性が大幅に向上したので、設計思想と実装ポイントをまとめて解説します。
結論からいうと、権限管理を以下の 3レイヤー に分けて整理するのが圧倒的に良かった。
- RBAC(役割): owner / admin / member / viewer
- 細かい権限フラグ: can_invite / can_manage_members / permissions(JSON)
- RLS(Row Level Security): DBが最終的に読み書きを拒否するガード
この3つをきちんと分離すると、柔軟なのに破綻しない権限システムが実現できます。
1. RBAC(役割)=“標準の権限”を定義する場所
まず、RBAC(Role-Based Access Control) を「基本の権限ルール」として定義しました。
4つのロール
- owner
- admin
- member
- viewer
ここで重要なのは、「普通のSaaSならこう動くよね」という標準挙動はすべて RBAC に書く」 という点です。
例えば次のような権限が getBaseRolePermissions によってロールごとに定義されます。
- canViewTeam
- canPostTeamLogs
- canEditOwnLogs
- canEditAnyTeamLog
- canInvite
- canManageMembers
- canManageSettings
owner / admin はフル権限、
member は投稿・自分のログ編集まで、
viewer は閲覧のみ。
📝 ポイント
RBACは「基本の職務」を書く場所。
例外はここに入れない。混ぜると破綻します。
2. 細かい権限フラグ=RBACの「例外」を上書きするレイヤー
次に、team_members テーブルに 例外フラグ を持たせています。
- can_invite
- can_manage_members
- permissions(JSON)
これらは「個別の例外ルール」を表すもので、RBAC で決めたベースラインを 部分的に補正 するためだけに存在します。
can_invite / can_manage_members の設計
- owner/admin → デフォルト true(false を入れると 制限 できる)
- member/viewer → デフォルト false(true を入れると 権限委譲 できる)
これにより:
- 「メンバーだけど招待だけできる」
- 「管理者だけどメンバー管理はさせない」
といった便利な例外を柔軟に作れるようになりました。
permissions(JSON) の役割
任意スキーマの JSON を使うことで、アプリの成長に合わせて拡張できます。
例:
{
"edit_logs": "team" | "own" | "none"
}
team → admin 相当の編集権を個別に付与
own → member/viewer に 投稿編集権 を追加
none → ログ編集一切不可(監査役のような役割の実現)
最終的な権限は getEffectivePermissions で一元管理
ロール(基本)
+例外フラグ(上書き)
+permissions(JSON)(拡張)
これらをすべて統合し、 最終的な「その人の権限」を1つのオブジェクトに変換 します。
API ではこの perms のみを参照すればよく、権限チェックの散在を完全に防げます。
3. RLS(Row Level Security)=DBレベルでの最終チェック
最後のレイヤーは、Supabase(Postgres)の RLS です。
アプリのバグすら突破できない最強のガードライン と考えると分かりやすいです。
読み取り(SELECT)は share_type で厳密に制御
- 自分のログ
- 所属チームの team 共有ログ
- specific(個別共有)で shared_with に含まれているログ
share_type × team_id × team_members × shared_with をすべて参照して、
「何が見えるか」を厳密に判定しています。
書き込み(INSERT / UPDATE / DELETE)も最小限に
- INSERT: 必ず自分のログとしてしか作成できない
- UPDATE / DELETE:
- 投稿者 → 常に可
- チーム owner/admin → チームログの編集・削除可
- member / viewer → RLSレベルでは不可(UIで非表示にする)
RLSは「最低限これだけは守る」という防衛線に徹し、アプリ側の柔軟な権限は RLS では扱いません。
3. レイヤーで設計すると得られるメリット
✔ 柔軟なのに破綻しない
カスタムの例外ルールをいくら追加しても、
RBACが基準
RLSが最後の砦
という土台のおかげで壊れにくい。
✔ APIコードが劇的に分かりやすくなる
各エンドポイントで
const perms = getEffectivePermissions(...)
if (!perms.canInvite) return 403
と書くだけで済む。
権限チェックの散在がなくなり、保守性が跳ね上がる。
✔ 設計の迷いが消える
新機能を作るとき、
標準動作 → RBAC
特定ユーザーの例外 → フラグ
最低限守るべき境界 → RLS
の3ステップで迷わず設計できる。
✔ 開発者が増えても破綻しにくい
ロールと例外の整理が明確なので、
新規参加の開発者でも「どこに何を書くべきか」すぐ理解できる。
まとめ:3レイヤー権限設計は、SaaS 開発の“最適解”
- RBAC → 「標準挙動」を明確化
- 権限フラグ & JSON → 個別例外を柔軟に上書き
- RLS → DBが守る最終防衛線
- API は常に getEffectivePermissions だけ見ればOK
- 機能追加時に迷わない
- 権限設計が壊れにくい
SaaS で権限管理を作り込む場合、この3レイヤー構成は非常におすすめできるアプローチです。
Discussion