はじめに — 認証の次の戦場、認可
過去数年で業界は認証(authentication)の問題をかなりの部分解決しました。OIDC/SAML ベースの SSO は常識となり、パスキーがパスワードを置き換えつつあります。しかし「誰であるかの確認」の次の問い — **「このユーザーはこのリソースにこの操作をしてよいのか?」** — は、依然として各アプリケーションに散らばった if 文の塊として残っているのがほとんどです。
2026年に認可(authorization)が再び熱いテーマになった背景は明確です。
1. **マイクロサービスとマルチテナンシー**: 権限判定ロジックが数十のサービスに重複実装され、一貫性と監査が不可能になりました。
2. **コラボレーション機能の普遍化**: 「このドキュメントをこの人に共有」という Google Docs 式の要件は、伝統的なロールベースモデルでは表現できません。
3. **AI エージェントの登場**: 人間ではない主体がリソースにアクセスするようになり、きめ細かく(fine-grained)監査可能な認可が必須になりました。
4. **規制**: 最小権限の原則(least privilege)の証明を要求するコンプライアンスが増えました。
本記事は RBAC → ABAC → ReBAC と続く認可モデルの進化を追跡し、[Google Zanzibar 論文](https://research.google/pubs/pub48190/)の中核概念と、そのオープンソース実装である [OpenFGA](https://openfga.dev/docs) の実践モデリング、さらにマイクロサービス環境でのアーキテクチャパターンまで扱います。
まず用語を整理します。
| 用語 | 意味 |
| --- | --- |
| AuthN(認証) | あなたが誰かを確認 — OIDC、SAML、パスキーの領域 |
| AuthZ(認可) | あなたが何をできるかを決定 — 本記事のテーマ |
| PEP | Policy Enforcement Point — 決定を執行する地点(API ゲートウェイ、サービスコード) |
| PDP | Policy Decision Point — 許可/拒否を決定する地点(認可エンジン) |
| PAP | Policy Administration Point — ポリシーを管理する地点 |
| PIP | Policy Information Point — 決定に必要な属性を供給する地点 |
RBAC — そして Role Explosion という呪い
RBAC(Role-Based Access Control)は最も広く使われているモデルです。ユーザーにロール(role)を付与し、ロールに権限(permission)を束ねます。
ユーザー ──(割当)──> ロール ──(保有)──> 権限
jane ──────────> editor ─────────> document:write
john ──────────> viewer ─────────> document:read
RBAC の長所は明白です。理解しやすく、監査が単純で(「editor ロール保有者の一覧を出力」)、NIST RBAC 標準(INCITS 359)という成熟した理論基盤もあります。
問題は、**現実の権限要件が「ロール」という単一次元では表現できない**ことです。
- 「editor だが、自部署のドキュメントだけ」
- 「viewer だが、勤務時間内だけ」
- 「manager だが、自分が起案した案件は承認不可」
こうした要件を RBAC に押し込むと、ロールが次元の組み合わせの数だけ爆発します。これが悪名高い **role explosion** です。
editor → 部署の次元を追加 → editor-sales
editor-hr
editor-engineering
→ 地域の次元を追加 → editor-sales-kr
editor-sales-us
editor-hr-kr
...(組み合わせ爆発)
実際の大企業の IGA 監査で「ユーザー数よりロール数が多い」事例は珍しくありません。ロールが数千個になった瞬間、RBAC の唯一の長所だった「理解しやすさ」が消えます。
それでも RBAC は死にません。**組織レベルの粗い権限区分(coarse-grained)** — 例: admin/member/billing-manager — には依然として最適です。問題は、リソース単位のきめ細かい制御を RBAC でやろうとするときに生じます。
ABAC — 属性とポリシーで、しかし XACML の教訓
ABAC(Attribute-Based Access Control)は、主体(subject)、リソース(resource)、行為(action)、環境(environment)の**属性**を条件式で評価します。
許可条件:
subject.department == resource.department
AND action == "edit"
AND environment.time BETWEEN 09:00 AND 18:00
AND subject.clearance >= resource.classification
role explosion の問題はエレガントに解決されます。部署が100あってもポリシーは1行です。
ABAC の標準化の試みが OASIS [XACML](https://docs.oasis-open.org/xacml/3.0/xacml-3.0-core-spec-os-en.html) でした。XACML は PEP/PDP/PAP/PIP というアーキテクチャ語彙を残した点で遺産は大きいものの、それ自体は事実上失敗した標準と評価されています。理由は次のとおりです。
- **XML ベースの極端な冗長性**: 簡単なルールひとつが数十行の XML になります。
- **デバッグ不可能性**: ポリシー結合(combining algorithm)の結果を人間が予測しにくい。
- **開発者体験の欠如**: 専門ツールなしにはポリシー作成がほぼ不可能でした。
XACML の精神はその後、**policy-as-code** 陣営 — 特に [OPA(Open Policy Agent)](https://www.openpolicyagent.org/docs/latest/)と Rego 言語 — に受け継がれます。同じ ABAC 評価をはるかに扱いやすい形で実装したものです。
しかし ABAC にも構造的な限界があります。**「jane はこの特定のドキュメントを見られるか」にはよく答えますが、「jane が見られるドキュメントの全リスト」(reverse query)を出すのは苦手**です。また「ドキュメント X をフォルダ Y に入れ、フォルダ Y はチーム Z に共有された」のように、**関係の連鎖**から権限が派生するシナリオは属性でモデリングするには不自然です。
ReBAC — Google Zanzibar が提示したパラダイム
ここで登場したのが ReBAC(Relationship-Based Access Control)です。権限を属性の条件式ではなく、**主体とオブジェクト間の関係グラフ**としてモデリングします。その頂点が2019年に発表された Google の [Zanzibar 論文](https://research.google/pubs/pub48190/)です。Zanzibar は Google Docs、Drive、YouTube、Cloud 全体の認可を担う単一のグローバルシステムで、数兆件の ACL を保存し、毎秒数百万件の権限クエリを 10ms 程度のレイテンシで処理します。
中核概念 1 — Relationship Tuple
Zanzibar のすべての権限データは、次の形のタプルで表現されます。
object#relation@user
例:
doc:budget-2026#owner@user:jane jane は budget-2026 ドキュメントの owner
doc:budget-2026#viewer@group:finance#member
finance グループの member は viewer
folder:q1#parent@doc:budget-2026 budget-2026 の親は q1 フォルダ
2つ目の例のように、user の位置に**別のオブジェクトの userset** を置けるのが強力です。「finance グループのメンバー全員」という集合を1行で指せて、グループのメンバーシップが変わってもドキュメント側のタプルはそのままです。
中核概念 2 — Userset Rewrite
タプルがデータなら、userset rewrite ルールは**権限の派生ロジック**です。たとえば「owner は自動的に editor でもある」「親フォルダの viewer は子ドキュメントの viewer である」といったルールをオブジェクトタイプごとに宣言します。
viewer 関係の評価ルール(概念的表現):
viewer =
直接 viewer に指定されたユーザー (this)
∪ editor であるユーザー (computed userset)
∪ parent フォルダで viewer のユーザー (tuple-to-userset)
この3つ — 直接の関係、計算された関係(computed userset)、関係をたどって越えていく関係(tuple-to-userset) — の和集合/積集合/差集合の組み合わせだけで、Google Drive レベルの共有モデルが表現できます。
中核概念 3 — Zookie と一貫性
分散システムにおける認可の古典的な罠は **「new enemy problem」** です。たとえば (1) jane をドキュメントから削除し、(2) ドキュメントに機密内容を追加したとき、レプリケーション遅延のせいで (2) の読み取り時点で (1) が未反映だと、削除されたはずの jane が新しい内容を見てしまいます。順序が保証されないキャッシュ/レプリケーションは、セキュリティインシデントになります。
Zanzibar は **zookie** という一貫性トークンでこれを解決します。コンテンツを変更するときに zookie を受け取って保存しておき、権限チェック時にその zookie を一緒に送ると、「少なくともその時点以降の ACL 状態」での評価が保証されます。外部一貫性(external consistency)と性能(大胆なキャッシング)のバランスを取るエレガントな仕掛けです。OpenFGA では consistency パラメータ(例: HIGHER_CONSISTENCY)で同様の制御を提供しています。
OpenFGA 実践モデリング — ドキュメント共有システム
[OpenFGA](https://openfga.dev/docs) は Auth0/Okta が始めて CNCF に寄贈した Zanzibar 実装です。DSL が直感的で、ReBAC 入門に最適です。Google Drive に似たドキュメント共有システムをモデリングしてみましょう。
model
schema 1.1
type user
type group
relations
define member: [user]
type folder
relations
define owner: [user]
define parent: [folder]
define editor: [user, group#member] or owner or editor from parent
define viewer: [user, group#member] or editor or viewer from parent
type doc
relations
define parent: [folder]
define owner: [user]
define editor: [user, group#member] or owner or editor from parent
define viewer: [user, group#member] or editor or viewer from parent
define can_share: owner or editor
define can_delete: owner
このモデルひとつで表現できること:
- ユーザー/グループ単位の共有(group#member の型制約)
- owner ⊃ editor ⊃ viewer の権限階層(computed relation)
- フォルダ権限の下位への継承(「editor from parent」 — tuple-to-userset)
- 行為レベルの権限(can_share、can_delete)
タプルを書き込んでクエリしてみます。
タプル書き込み: finance グループのメンバーに q1 フォルダの viewer を付与
fga tuple write --store-id "$FGA_STORE_ID" \
"group:finance#member" viewer "folder:q1"
jane を finance グループに追加
fga tuple write --store-id "$FGA_STORE_ID" \
"user:jane" member "group:finance"
budget-2026 ドキュメントを q1 フォルダに配置
fga tuple write --store-id "$FGA_STORE_ID" \
"folder:q1" parent "doc:budget-2026"
クエリ: jane は budget-2026 を見られるか?
fga query check --store-id "$FGA_STORE_ID" \
"user:jane" viewer "doc:budget-2026"
→ allowed: true (group → folder → doc の関係連鎖から派生)
アプリケーションコードでは SDK でチェックします。
const fga = new OpenFgaClient({
apiUrl: process.env.FGA_API_URL,
storeId: process.env.FGA_STORE_ID,
});
// 単発チェック(PEP から呼び出す)
const { allowed } = await fga.check({
user: 'user:jane',
relation: 'viewer',
object: 'doc:budget-2026',
});
// 逆クエリ: jane が閲覧可能なドキュメント一覧(UI フィルタリング用)
const { objects } = await fga.listObjects({
user: 'user:jane',
relation: 'viewer',
type: 'doc',
});
ABAC が苦手としていた **reverse query(ListObjects)がファーストクラスの API** である点に注目してください。「自分がアクセス可能なリソース一覧」画面を作るときに決定的な差を生みます。
エコシステム — SpiceDB、Ory Keto、そして OPA との役割分担
Zanzibar 系の実装は OpenFGA だけではありません。
| プロジェクト | 特徴 | スキーマ言語 |
| --- | --- | --- |
| OpenFGA | CNCF、Auth0 出身、最も親切な DSL とドキュメント | FGA DSL / JSON |
| SpiceDB (AuthZed) | Zanzibar 論文に最も忠実、caveat(条件付き関係)対応 | SpiceDB schema |
| Ory Keto | Ory エコシステム(Kratos/Hydra)と統合 | Ory Permission Language |
| Permify | マルチテナンシーを強調 | Permify schema |
では OPA/Rego はどこに入るのでしょうか。**OPA はポリシー評価エンジンであり、Zanzibar 系は関係データストア兼グラフ評価エンジン**です。大まかに分けると:
- **OPA が得意なこと**: 入力として与えられたコンテキストに対するルール評価。「この K8s マニフェストはセキュリティ基準を満たすか」「このリクエストの JWT scope はこの API に十分か」のようなステートレスな判定。ポリシーがコードとしてバージョン管理される policy-as-code。
- **ReBAC エンジンが得意なこと**: 「jane → グループ → フォルダ → ドキュメント」のように、**保存された関係データ**をグラフとして探索する必要がある判定。数億件の関係の上での check/list クエリ。
リクエスト → API Gateway/サービス (PEP)
│
├─ 粗い判定: JWT scope、テナント検証 → OPA (ステートレスポリシー)
│
└─ 細かい判定: このユーザーがこのドキュメントを? → OpenFGA (関係グラフ)
両者は競合ではなくレイヤーの異なるツールであり、併用する組み合わせが一般的です。Rego の例で感覚をつかんでみましょう。
OPA Rego — coarse-grained API ポリシー(概念例)
package httpapi.authz
default allow := false
allow if {
input.method == "GET"
startswith(input.path, "/api/public/")
}
allow if {
input.method == "POST"
input.path == "/api/docs"
"docs:write" in input.token.scopes
input.token.tenant == input.headers["x-tenant-id"]
}
アーキテクチャパターン — 中央集権 PDP vs ライブラリ埋め込み
認可エンジンの配置方法は大きく2通りです。
パターン A: 中央集権 PDP パターン B: サイドカー/ライブラリ埋め込み
───────────────────── ─────────────────────────────
svc-a ──┐ svc-a ── [PDP sidecar/lib]
svc-b ──┼──> AuthZ Service (PDP) svc-b ── [PDP sidecar/lib]
svc-c ──┘ + 単一の関係 DB svc-c ── [PDP sidecar/lib]
(ポリシー/データを複製配布)
長所: 単一の真実の源、 長所: レイテンシ最小(ローカル評価)、
監査が容易、モデルの一貫性 ネットワーク依存なし
短所: ネットワークホップ追加、 短所: データ同期が複雑、
可用性が全体に影響 一貫性の保証が困難
実務上のガイドラインは次のとおりです。
- **関係データに基づく判定(ReBAC)は中央集権が基本**です。関係グラフを全サービスに複製するのは一貫性の悪夢です。レイテンシは同一 AZ 配置 + キャッシングで 1〜5ms レベルまで下げられます。
- **ステートレスなポリシー(OPA)は埋め込みが自然**です。OPA はサイドカー/ライブラリとして各サービスの横に置き、ポリシーバンドルを中央(PAP)から配布するモデルが標準です。
- 中央 PDP の可用性は認証 IdP と同格に扱うべきです。冗長化、ヘルスチェックベースのフェイルオーバー、そして「PDP 障害時は fail-closed」が原則です(fail-open は認可バイパスです)。
Keycloak Authorization Services と外部エンジンの組み合わせ
[Keycloak](https://www.keycloak.org/docs/latest/authorization_services/index.html) にも独自の認可機能(Authorization Services)があります。UMA 2.0 ベースで resource/scope/policy/permission を定義し、トークンに権限(RPT)を載せて送る方式です。では Keycloak だけで十分でしょうか?
現実的な役割分担はこうです。
| レイヤー | 担当 | ツール |
| --- | --- | --- |
| アイデンティティ/ロール発行 | ユーザーが誰で、どの粗いロールを持つか(トークンクレーム) | Keycloak |
| API レベルのポリシー | このトークンでこのエンドポイントを呼べるか | Keycloak AuthZ または OPA |
| リソースレベルの関係 | このユーザーがこのドキュメント/プロジェクト/チケットに何を | OpenFGA/SpiceDB |
典型的な組み合わせパターン: Keycloak が発行したトークンの sub クレームを OpenFGA の user 識別子として使い、ロール/グループクレームの変更をイベントで受けて FGA タプルに同期します。
ログイン → Keycloak → access_token (sub: user:jane, groups: [finance])
│
リクエスト → サービス(PEP) ─┤
├─ トークン検証 (署名, aud, exp) ← Keycloak 公開鍵
└─ fga.check(user:jane, viewer, doc:X) ← OpenFGA
Keycloak のグループメンバーシップを FGA タプルに同期するイベントリスナーの概念例です。
// Keycloak SPI — グループメンバーシップ変更を OpenFGA に反映するイベントリスナー(概念コード)
public class FgaSyncEventListener implements EventListenerProvider {
@Override
public void onEvent(AdminEvent event, boolean includeRepresentation) {
if (event.getResourceType() == ResourceType.GROUP_MEMBERSHIP) {
String userId = extractUserId(event.getResourcePath());
String groupId = extractGroupId(event.getResourcePath());
if (event.getOperationType() == OperationType.CREATE) {
fgaClient.writeTuple("user:" + userId, "member", "group:" + groupId);
} else if (event.getOperationType() == OperationType.DELETE) {
fgaClient.deleteTuple("user:" + userId, "member", "group:" + groupId);
}
}
}
}
マイクロサービスでの認可データ同期
ReBAC エンジンを導入すると新しい運用課題が生まれます。**アプリケーション DB の事実(所有権、メンバーシップ、フォルダ構造)と FGA のタプルをどう一致させるか**です。
戦略は3つあります。
1. **同期的な二重書き込み(dual write)**: リソース作成トランザクションの中で FGA への書き込みも行う。単純ですが、部分失敗(DB は成功、FGA は失敗)時に権限の穴が生じます。補償トランザクションまたはリトライキューが必須です。
2. **Transactional Outbox + CDC**: 推奨パターン。リソース変更と outbox レコードを1トランザクションにまとめ、Debezium などの CDC が outbox を読んで FGA に反映します。at-least-once 配信なので、タプル書き込みは冪等に設計します。
3. **定期的な突合(reconciliation)**: 上の2戦略と並行して、バッチでアプリケーション DB と FGA タプルの差分を比較・修復します。認可データの drift はすなわちセキュリティ脆弱性なので、突合は選択ではなく必須です。
サービスのトランザクション
┌──────────────────────────────┐
│ INSERT INTO documents (...) │
│ INSERT INTO outbox ( │ CDC (Debezium) OpenFGA
│ event: doc.created, │ ───────────────────────> tuple write
│ owner: user:jane ) │ (at-least-once) (冪等処理)
└──────────────────────────────┘
▲
nightly reconciliation ───┘ (drift の検知/修復)
削除はさらに厄介です。リソースが削除されたら関連タプルをすべて消さなければならず(孤児タプルは監査ノイズ + 潜在的な誤判定)、フォルダのように階層を持つオブジェクトは、下位オブジェクトのタプル整理の順序まで設計する必要があります。
選定ガイド — 何をいつ使うか
| 状況 | 推奨モデル |
| --- | --- |
| 社内 admin ツール、ロールが5個以下で安定 | RBAC(IdP のロールクレームで十分) |
| テナント/部署/時間など属性条件が中心 | ABAC(OPA policy-as-code) |
| ドキュメント/フォルダ/プロジェクト共有、階層継承、コラボ機能 | ReBAC(OpenFGA/SpiceDB) |
| K8s admission、インフラポリシー、CI ゲート | OPA/Rego |
| 規制業界 + アクセス決定の中央監査が必要 | 中央 PDP + 決定ログパイプライン |
| 上記の要件が混在(ほとんどの現実) | RBAC(粗く)+ ReBAC(細かく)+ OPA(ポリシー)の組み合わせ |
判断基準を質問の形で整理すると:
1. **「X を見られる人の一覧」または「自分が見られる X の一覧」が必要か?** → 必要なら ReBAC。ABAC では苦しみます。
2. **権限がリソース間の関係(フォルダ→ドキュメント、組織→プロジェクト)から派生するか?** → ReBAC。
3. **権限がリクエストコンテキスト(時間、IP、デバイス状態)に依存するか?** → ABAC/OPA。ただし SpiceDB の caveat や OpenFGA の conditions で ReBAC に条件を混ぜることも可能です。
4. **組織全体で権限モデルを統一する準備ができているか?** → できていなければ、1つのドメイン(例: ドキュメントサービス)から ReBAC を導入し、段階的に拡張してください。
アンチパターン集
1. **JWT にきめ細かい権限を全部載せる**: トークンにドキュメント ID のリストを入れるとトークンが肥大化し、権限の取り消しがトークン失効まで遅延します。トークンにはアイデンティティと粗いクレームのみ、細かい判定は PDP へのリアルタイムクエリで。
2. **fail-open な PDP**: 認可サービス障害時に「とりあえず許可」は認可バイパスのバックドアです。fail-closed + 短い TTL のキャッシュ済み決定の許容が定石です。
3. **フロントエンドの認可を信頼する**: UI でボタンを隠すのは UX であってセキュリティではありません。すべての執行はサーバー側の PEP で。
4. **関係モデルなしにタプルから積み上げる**: モデル(スキーマ)レビューなしにタプルを蓄積すると、後でマイグレーション地獄が来ます。モデル変更はコードレビュー + テスト(FGA の model test)を通してください。
5. **決定ログの未収集**: 「なぜ許可されたのか」を再構成できなければ監査対応は不可能です。check のリクエスト/レスポンスを構造化ログとして残してください。
6. **二重書き込み後の突合の省略**: drift は必ず発生します。検知されない drift は沈黙する権限バグです。
おわりに
認可モデルの進化は「表現力」と「運用可能性」の間の緊張の中で進んできました。RBAC はシンプルでしたが role explosion で崩れ、ABAC(XACML)は表現力を得ましたが開発者体験を失い、Zanzibar/ReBAC は関係という自然なモデルと reverse query、そして zookie という一貫性の仕掛けで現在の均衡点を提示しました。
実務上の結論は意外にも保守的です。**粗い権限は IdP(Keycloak)のロールで、ポリシー的な判定は OPA で、リソースレベルの関係は OpenFGA で** — 各レイヤーに合ったツールを組み合わせつつ、すべての決定を監査可能な形で残すこと。それが2026年の認可アーキテクチャの模範解答です。
次の記事では、この認可インフラの上に乗る新しい主体 — AI エージェントのアイデンティティと MCP 認証を扱います。
参考資料
- [Zanzibar: Google Consistent, Global Authorization System](https://research.google/pubs/pub48190/) — Zanzibar 論文原文
- [OpenFGA Documentation](https://openfga.dev/docs) — OpenFGA 公式ドキュメント
- [OpenFGA Modeling Guide](https://openfga.dev/docs/modeling) — モデリング実践ガイド
- [SpiceDB Documentation](https://authzed.com/docs) — SpiceDB/AuthZed ドキュメント
- [Ory Keto Documentation](https://www.ory.sh/docs/keto) — Ory Keto ドキュメント
- [Open Policy Agent Documentation](https://www.openpolicyagent.org/docs/latest/) — OPA/Rego 公式ドキュメント
- [OASIS XACML 3.0 Specification](https://docs.oasis-open.org/xacml/3.0/xacml-3.0-core-spec-os-en.html) — XACML 標準
- [NIST RBAC — INCITS 359](https://csrc.nist.gov/projects/role-based-access-control) — RBAC 標準プロジェクト
- [Keycloak Authorization Services Guide](https://www.keycloak.org/docs/latest/authorization_services/index.html) — Keycloak の認可機能
- [RFC 9700 — Best Current Practice for OAuth 2.0 Security](https://datatracker.ietf.org/doc/html/rfc9700) — OAuth セキュリティ BCP
현재 단락 (1/238)
過去数年で業界は認証(authentication)の問題をかなりの部分解決しました。OIDC/SAML ベースの SSO は常識となり、パスキーがパスワードを置き換えつつあります。しかし「誰であるか...