メインコンテンツへ移動
SEMentor
セキュリティ 約12分

認可と権限設計

RBAC・ABAC・所有者チェック・最小権限の考え方を、APIとデータ設計に落とし込んで安全な認可を実装する

ログインできることと、操作してよいことは別

認証は「この人は誰か」を確認する仕組みです。一方で認可は「その人が、この対象に、この操作をしてよいか」を判断する仕組みです。

ログイン機能が正しく動いていても、認可が弱いと次のような事故が起きます。

  • 一般ユーザーが管理者APIを呼び出せる
  • 他人の注文・請求書・プロフィールをURL変更だけで閲覧できる
  • 退職者や契約終了ユーザーが古い権限を持ち続ける
  • 画面では非表示のボタンが、API直叩きでは実行できる

認可はUIの表示制御ではなく、サーバー側で守るべきセキュリティ境界です。

認可判断の3要素

認可は、次の3つを組み合わせて判断します。

要素意味
Subject誰がuser_123, admin, support_agent
Action何をするかread, create, update, delete, approve
Resource何に対してinvoice_456, project_789, user_profile

たとえば「山田さんは請求書456を編集してよいか」は、次のように表せます。

Subject: user_123
Action: update
Resource: invoice_456
Decision: allow / deny

重要なのは、「ユーザーがログイン済みか」だけで判断しないことです。ログイン済みでも、対象リソースに対する権限がなければ拒否します。

権限モデルの基本パターン

RBAC: ロールで管理する

RBAC(Role-Based Access Control)は、ユーザーにロールを付与し、ロールごとに操作権限を定義する方式です。

ロールできること
admin全ユーザーの閲覧・編集・削除
manager所属チームの閲覧・承認
member自分のデータ閲覧・編集
viewer閲覧のみ

RBACは分かりやすく、業務システムでもよく使われます。ただし、ロールが増えすぎると管理が難しくなります。

users
- id
- name

roles
- id
- name

permissions
- id
- action
- resource_type

user_roles
- user_id
- role_id

role_permissions
- role_id
- permission_id

ABAC: 属性で判断する

ABAC(Attribute-Based Access Control)は、ユーザーやリソースの属性を見て判断します。

「請求書の部署ID」と「ユーザーの所属部署ID」が一致する場合だけ閲覧できる
「金額が100万円以上」の承認は部長以上だけ可能
「公開状態がdraft」の記事は作成者と編集者だけ閲覧できる

RBACだけでは表現しにくい細かな条件を扱えますが、ルールが複雑になりやすいため、ポリシーを分かりやすく管理する設計が必要です。

所有者チェック

最も基本的で、最も漏れやすいのが所有者チェックです。

// 危険: IDだけで取得している
const invoice = await db.invoice.findUnique({
  where: { id: invoiceId },
});

この実装では、ログインユーザーが別ユーザーの invoiceId を知っていれば閲覧できる可能性があります。これは IDOR(Insecure Direct Object Reference)と呼ばれる典型的な認可不備です。

// 安全: ログインユーザーとの関係も条件に含める
const invoice = await db.invoice.findFirst({
  where: {
    id: invoiceId,
    ownerId: currentUser.id,
  },
});

if (!invoice) {
  throw new ForbiddenError("この請求書を閲覧する権限がありません");
}

認可チェックをどこに置くか

認可チェックは、UIではなくAPI・ユースケース層・データアクセス層の境界に置きます。

避けたい実装

// UIでは削除ボタンを非表示にしている
{user.role === "admin" && <button>削除</button>}

// しかしAPIでは権限を見ていない
app.delete("/users/:id", async (req, res) => {
  await deleteUser(req.params.id);
  res.sendStatus(204);
});

画面上のボタンを隠しても、APIを直接呼ばれれば実行できてしまいます。

守りやすい実装

app.delete("/users/:id", async (req, res) => {
  const currentUser = requireLogin(req);

  await authorize(currentUser, "delete", {
    type: "user",
    id: req.params.id,
  });

  await deleteUser(req.params.id);
  res.sendStatus(204);
});

権限判断を authorize() に集約すると、APIごとに条件が散らばりにくくなります。

安全な認可設計の原則

デフォルト拒否

迷ったら許可ではなく拒否します。

function can(user: User, action: Action, resource: Resource): boolean {
  const rule = findRule(user, action, resource);
  if (!rule) return false;
  return rule.allows;
}

「ルールが見つからない場合は許可」は危険です。新しいAPIや新しいリソースを追加したとき、認可ルールの定義漏れがそのまま脆弱性になります。

最小権限

ユーザーやシステムには、必要最小限の権限だけを与えます。

  • 読み取り専用ユーザーに更新権限を付けない
  • 管理者権限を日常業務のアカウントに付けっぱなしにしない
  • バッチ処理用トークンに全API権限を持たせない
  • 一時的な権限には期限を設定する

権限は「便利だから広めに付ける」のではなく、「必要になったら追加する」考え方が安全です。

監査ログを残す

重要操作は、誰が、いつ、何に対して、何をしたかを残します。

{
  "actorId": "user_123",
  "action": "approve",
  "resourceType": "invoice",
  "resourceId": "invoice_456",
  "result": "allow",
  "timestamp": "2026-04-26T09:00:00Z"
}

監査ログは、障害調査・不正操作の追跡・権限棚卸しに役立ちます。特に管理画面、承認、削除、個人情報閲覧では必須に近い要件です。

権限設計チェックリスト

設計・レビュー時は次の観点を確認します。

□ API側で必ず認可チェックしているか
□ URLのIDを変えるだけで他人のデータにアクセスできないか
□ ロールだけでなく、所有者・部署・状態なども見ているか
□ デフォルト拒否になっているか
□ 管理者権限が広すぎないか
□ 退職・異動・契約終了時に権限を外せるか
□ 重要操作の監査ログを残しているか
□ テストで「許可されるケース」と「拒否されるケース」を両方確認しているか

まとめ

認可は、ログイン後の安全性を支える最後の砦です。

  • 認証は「誰か」、認可は「何をしてよいか」を判断する
  • 権限判断は subject / action / resource で整理する
  • UIの非表示だけで守らず、API側で必ず検証する
  • デフォルト拒否・最小権限・監査ログを設計に組み込む

認可不備は、画面上では正常に見えても、APIやID指定で簡単に露呈します。仕様書や画面設計の段階から「誰が、どのデータに、どの操作をしてよいか」を明文化しましょう。


認証方式の整理は認証・認可の仕組み、主要なWeb脆弱性はWebセキュリティ基礎で学べます。

このレッスンは未完了です。