🙆‍♀️

RBAC × 例外フラグ × RLS の3レイヤー構成にしたら、権限まわりが一気に“壊れにくく”なった話

に公開

最近、チームログ共有アプリの権限まわりを大きく改修しました。
結果として、アプリ全体の一貫性・保守性・安全性が大幅に向上したので、設計思想と実装ポイントをまとめて解説します。

結論からいうと、権限管理を以下の 3レイヤー に分けて整理するのが圧倒的に良かった。

  1. RBAC(役割): owner / admin / member / viewer
  2. 細かい権限フラグ: can_invite / can_manage_members / permissions(JSON)
  3. 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