Dev Study
← 解説「認可とロール — RBAC」に戻る

サンプルコードで身につける: 認可とロール — RBAC

解説で学んだ概念を、5つの具体例で確認します。素朴な例から実務寄りの例へと進みます。

1認証と認可の違い

「誰か(認証)」と「何をしてよいか(認可)」の違いをコメントで整理した例です。認可は本人が確定して初めて成立するため、必ず認証の後に行う、という順序関係を押さえます。

// 認証(Authentication)と認可(Authorization)は別物
//
//   認証 … 「あなたは誰か」を確かめる
//          例: トークンが正しいか → user = { id: 1, role: "viewer" }
//
//   認可 … 「その人が何をしてよいか」を判定する
//          例: この削除APIは admin だけ → viewer は 403
//
// 順序が大事: 認証(誰か)が決まらないと認可(権限)は判定できない
//   ① JWT 等でユーザーを特定  → ② そのロールで許可/拒否を決める

2@Roles デコレータでロールを宣言する

ハンドラに必要なロールを宣言するカスタムデコレータ @Roles の作り方です。SetMetadata で要求ロールをメタデータとして埋め込み、後段のガードがそれを読み取る、という分業の前半部分を示します。

import { SetMetadata } from "@nestjs/common";

export type Role = "admin" | "editor" | "viewer";

// @Roles("admin") でハンドラに「必要なロール」を埋め込む
export const ROLES_KEY = "roles";
export const Roles = (...roles: Role[]) => SetMetadata(ROLES_KEY, roles);

// 使う側(宣言するだけ。判定はガードが行う)
// @Roles("admin")
// @Delete(":id")
// remove(@Param("id") id: string) { ... }

3ロール判定ロジックを再現する

ガードの核心である「要求ロールとユーザーのロールの突き合わせ」をプレーン TypeScript で再現した例です。要求ロールが無ければ全員許可、有れば1つでも持てば通過、という最も基本的な OR 判定を示します。

TypeScript

4ロール階層を再現する

admin > editor > viewer のように上位ロールが下位の権限も満たす「階層型」RBAC をプレーン TypeScript で再現した例です。ロールにレベルを与えて比較するだけで、上位は下位の操作を自動的に許可できます。

TypeScript

5RolesGuard でエンドポイントを保護する

@Roles のメタデータを Reflector で読み取り、ログインユーザーのロールと突き合わせる実務の RolesGuard です。JWT でユーザーを特定した後にこのガードを通すことで、ロール別のエンドポイント保護が完成します。

import { CanActivate, ExecutionContext, Injectable } from "@nestjs/common";
import { Reflector } from "@nestjs/core";
import { ROLES_KEY, Role } from "./roles.decorator";

@Injectable()
export class RolesGuard implements CanActivate {
  constructor(private readonly reflector: Reflector) {}

  canActivate(context: ExecutionContext): boolean {
    // @Roles(...) で埋めた要求ロールを取り出す
    const required = this.reflector.getAllAndOverride<Role[]>(ROLES_KEY, [
      context.getHandler(),
      context.getClass(),
    ]);
    if (!required || required.length === 0) return true; // 制限なし

    // JWT 認証済みの request.user(先に認証が必要)
    const { user } = context.switchToHttp().getRequest();
    return required.some((role) => user.roles.includes(role));
  }
}
// @UseGuards(JwtAuthGuard, RolesGuard) の順で適用する