NestJSでセキュアなAPIを構築する際、ロールベースアクセス制御(RBAC: Role-Based Access Control)は欠かせない要素です。管理者のみがアクセスできるエンドポイント、一般ユーザーには読み取り専用など、ロールに応じたきめ細かな権限制御を実装することで、堅牢なアプリケーションを構築できます。本記事では、@Roles()カスタムデコレータの作成からRolesGuardの実装、Reflectorを使用したメタデータ取得、そして階層的なロール設計パターンまでを実践的に解説します。
実行環境と前提条件#
本記事の内容を実践するにあたり、以下の環境を前提としています。
| 項目 |
バージョン・要件 |
| Node.js |
20以上 |
| npm |
10以上 |
| NestJS |
11.x |
| @nestjs/jwt |
11.x |
| TypeScript |
5.x |
| OS |
Windows / macOS / Linux |
| エディタ |
VS Code(推奨) |
事前に以下の準備を完了してください。
- NestJS CLIのインストール済み
- NestJSプロジェクトの作成済み
- JWT認証の実装済み(
request.userにユーザー情報が格納されている状態)
- Guardの基本概念の理解
NestJSプロジェクトの作成方法はNestJS入門記事、Guardの基本はGuard解説記事、JWT認証の実装はJWT認証記事を参照してください。
RBACとは何か#
RBAC(Role-Based Access Control)は、ユーザーにロール(役割)を割り当て、そのロールに基づいてリソースへのアクセス権限を制御する手法です。
flowchart TD
subgraph Users["ユーザー"]
U1[田中さん]
U2[鈴木さん]
U3[佐藤さん]
end
subgraph Roles["ロール"]
R1[Admin]
R2[Moderator]
R3[User]
end
subgraph Permissions["権限"]
P1[全リソース管理]
P2[コンテンツ編集]
P3[閲覧のみ]
end
U1 --> R1
U2 --> R2
U3 --> R3
R1 --> P1
R2 --> P2
R3 --> P3
style R1 fill:#E91E63,color:#fff
style R2 fill:#9C27B0,color:#fff
style R3 fill:#3F51B5,color:#fffRBACの利点#
| 利点 |
説明 |
| 管理の簡素化 |
ユーザーごとに権限を設定する代わりにロールを割り当てるだけで済む |
| 一貫性の確保 |
同じロールを持つユーザーは同じ権限を持つことが保証される |
| 監査の容易さ |
ロールと権限の対応が明確なためセキュリティ監査がしやすい |
| スケーラビリティ |
ユーザー数が増えても管理コストが増大しにくい |
NestJSにおけるRBACの設計アプローチ#
NestJSでRBACを実装する際は、以下のコンポーネントを組み合わせます。
flowchart LR
A[リクエスト] --> B[AuthGuard]
B --> C{認証済み?}
C -->|No| D[401 Unauthorized]
C -->|Yes| E[RolesGuard]
E --> F{ロール確認}
F --> G["@Roles()メタデータ取得"]
G --> H{必要なロールを持つ?}
H -->|No| I[403 Forbidden]
H -->|Yes| J[Controller]
style E fill:#FF5722,color:#fff
style G fill:#4CAF50,color:#fff
| コンポーネント |
役割 |
| Role Enum |
システム内で使用可能なロールを定義 |
@Roles()デコレータ |
ルートハンドラに必要なロールをメタデータとして付与 |
RolesGuard |
メタデータを読み取りユーザーのロールと照合 |
Reflector |
デコレータで設定されたメタデータを取得 |
ロールの定義#
まず、システムで使用するロールをEnumとして定義します。
Role Enumの作成#
1
2
3
4
5
6
7
|
// src/auth/enums/role.enum.ts
export enum Role {
User = 'user',
Moderator = 'moderator',
Admin = 'admin',
SuperAdmin = 'super_admin',
}
|
このEnumにより、ロール名を文字列リテラルとして管理するよりも型安全性が向上し、タイポによるバグを防止できます。
ユーザーエンティティへのロール追加#
ユーザーエンティティにロール情報を含める必要があります。以下は典型的な実装例です。
1
2
3
4
5
6
7
8
9
10
11
12
|
// src/users/entities/user.entity.ts
import { Role } from '../../auth/enums/role.enum';
export class User {
id: number;
username: string;
email: string;
password: string;
roles: Role[];
createdAt: Date;
updatedAt: Date;
}
|
TypeORMやPrismaを使用している場合は、それぞれのスキーマ定義に合わせてロールを永続化します。
@Roles()カスタムデコレータの作成#
NestJSでは、SetMetadata()またはReflector.createDecorator()を使用してカスタムデコレータを作成できます。それぞれの方法を解説します。
SetMetadata()は、任意のキーと値のペアをメタデータとしてルートハンドラに付与します。
1
2
3
4
5
6
|
// src/auth/decorators/roles.decorator.ts
import { SetMetadata } from '@nestjs/common';
import { Role } from '../enums/role.enum';
export const ROLES_KEY = 'roles';
export const Roles = (...roles: Role[]) => SetMetadata(ROLES_KEY, roles);
|
この方法では、ROLES_KEYを使用してメタデータを取得する必要があります。
方法2: Reflector.createDecoratorを使用する(推奨)#
NestJS 10以降では、Reflector.createDecorator()を使用した型安全なアプローチが推奨されています。
1
2
3
4
5
|
// src/auth/decorators/roles.decorator.ts
import { Reflector } from '@nestjs/core';
import { Role } from '../enums/role.enum';
export const Roles = Reflector.createDecorator<Role[]>();
|
このアプローチの利点は以下のとおりです。
| 観点 |
SetMetadata |
Reflector.createDecorator |
| 型安全性 |
手動で型を指定する必要あり |
自動的に型推論される |
| キー管理 |
文字列キーを定義・管理する必要あり |
不要(デコレータ自体がキーとなる) |
| コード量 |
やや冗長 |
シンプル |
本記事では、Reflector.createDecorator()を使用したアプローチで進めます。
デコレータの使用例#
作成した@Roles()デコレータは、Controllerのメソッドまたはクラスに適用できます。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
// src/articles/articles.controller.ts
import { Controller, Get, Post, Body, Delete, Param } from '@nestjs/common';
import { Roles } from '../auth/decorators/roles.decorator';
import { Role } from '../auth/enums/role.enum';
import { ArticlesService } from './articles.service';
import { CreateArticleDto } from './dto/create-article.dto';
@Controller('articles')
export class ArticlesController {
constructor(private readonly articlesService: ArticlesService) {}
// すべてのユーザーがアクセス可能(認証のみ必要)
@Get()
findAll() {
return this.articlesService.findAll();
}
// Moderator以上のロールが必要
@Post()
@Roles(Role.Moderator, Role.Admin, Role.SuperAdmin)
create(@Body() createArticleDto: CreateArticleDto) {
return this.articlesService.create(createArticleDto);
}
// Admin以上のロールが必要
@Delete(':id')
@Roles(Role.Admin, Role.SuperAdmin)
remove(@Param('id') id: string) {
return this.articlesService.remove(+id);
}
}
|
RolesGuardの実装#
RolesGuardは、リクエストを処理する前にユーザーのロールを検証するGuardです。Reflectorを使用してメタデータを取得し、ユーザーのロールと照合します。
基本的なRolesGuardの実装#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
// src/auth/guards/roles.guard.ts
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { Role } from '../enums/role.enum';
import { Roles } from '../decorators/roles.decorator';
@Injectable()
export class RolesGuard implements CanActivate {
constructor(private reflector: Reflector) {}
canActivate(context: ExecutionContext): boolean {
// メタデータからロールを取得
const requiredRoles = this.reflector.getAllAndOverride<Role[]>(Roles, [
context.getHandler(),
context.getClass(),
]);
// ロールが設定されていない場合はアクセスを許可
if (!requiredRoles) {
return true;
}
// リクエストからユーザー情報を取得
const { user } = context.switchToHttp().getRequest();
// ユーザーが必要なロールのいずれかを持っているか確認
return requiredRoles.some((role) => user.roles?.includes(role));
}
}
|
Reflectorのメソッド解説#
Reflectorクラスは、メタデータを取得するための複数のメソッドを提供しています。
| メソッド |
説明 |
使用例 |
get() |
指定したターゲット(ハンドラまたはクラス)からメタデータを取得 |
単一レベルでのメタデータ取得 |
getAllAndOverride() |
複数ターゲットを検索し、最初に見つかったメタデータを返す |
メソッドレベルがクラスレベルを上書き |
getAllAndMerge() |
複数ターゲットのメタデータを結合して返す |
クラスとメソッドのロールを合算 |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
// get() の使用例
const roles = this.reflector.get(Roles, context.getHandler());
// getAllAndOverride() の使用例(推奨)
const roles = this.reflector.getAllAndOverride(Roles, [
context.getHandler(), // メソッドレベルを優先
context.getClass(), // クラスレベルをフォールバック
]);
// getAllAndMerge() の使用例
const roles = this.reflector.getAllAndMerge(Roles, [
context.getHandler(),
context.getClass(),
]);
// 結果: メソッドとクラスのロールが結合された配列
|
メタデータ取得の優先順位#
getAllAndOverride()を使用する場合、以下の優先順位でメタデータが取得されます。
flowchart TD
A["getAllAndOverride()呼び出し"] --> B{メソッドに@Roles()あり?}
B -->|Yes| C[メソッドのロールを返す]
B -->|No| D{クラスに@Roles()あり?}
D -->|Yes| E[クラスのロールを返す]
D -->|No| F[undefined を返す]
style C fill:#4CAF50,color:#fff
style E fill:#2196F3,color:#fff
style F fill:#9E9E9E,color:#fffこれにより、クラスレベルでデフォルトのロールを設定し、特定のメソッドで上書きすることが可能になります。
1
2
3
4
5
6
7
8
9
10
11
|
@Controller('users')
@Roles(Role.User) // デフォルト: Userロール
export class UsersController {
@Get()
// Userロールが適用される
findAll() {}
@Delete(':id')
@Roles(Role.Admin) // 上書き: Adminロールのみ
remove(@Param('id') id: string) {}
}
|
Guardの登録と適用#
RolesGuardを適用するには、複数の方法があります。
方法1: メソッドレベルでの適用#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
import { Controller, Post, UseGuards } from '@nestjs/common';
import { AuthGuard } from '../auth/guards/auth.guard';
import { RolesGuard } from '../auth/guards/roles.guard';
import { Roles } from '../auth/decorators/roles.decorator';
import { Role } from '../auth/enums/role.enum';
@Controller('admin')
export class AdminController {
@Post('settings')
@UseGuards(AuthGuard, RolesGuard)
@Roles(Role.Admin)
updateSettings() {
return { message: 'Settings updated' };
}
}
|
方法2: コントローラレベルでの適用#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
@Controller('admin')
@UseGuards(AuthGuard, RolesGuard)
@Roles(Role.Admin)
export class AdminController {
@Get('dashboard')
getDashboard() {
return { message: 'Admin dashboard' };
}
@Post('settings')
updateSettings() {
return { message: 'Settings updated' };
}
}
|
方法3: グローバル登録(推奨)#
認可チェックをアプリケーション全体に適用する場合は、APP_GUARDトークンを使用してグローバルに登録します。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
// src/app.module.ts
import { Module } from '@nestjs/common';
import { APP_GUARD } from '@nestjs/core';
import { AuthGuard } from './auth/guards/auth.guard';
import { RolesGuard } from './auth/guards/roles.guard';
@Module({
providers: [
{
provide: APP_GUARD,
useClass: AuthGuard,
},
{
provide: APP_GUARD,
useClass: RolesGuard,
},
],
})
export class AppModule {}
|
グローバル登録の場合、Guardは定義順に実行されます。AuthGuardが先に登録されているため、認証 → 認可の順序で処理されます。
@Public()デコレータとの組み合わせ#
グローバルGuardを使用する場合、認証不要のエンドポイント(ログインなど)には@Public()デコレータを付与してスキップします。
1
2
3
4
5
|
// src/auth/decorators/public.decorator.ts
import { SetMetadata } from '@nestjs/common';
export const IS_PUBLIC_KEY = 'isPublic';
export const Public = () => SetMetadata(IS_PUBLIC_KEY, true);
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
// src/auth/guards/auth.guard.ts
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { IS_PUBLIC_KEY } from '../decorators/public.decorator';
@Injectable()
export class AuthGuard implements CanActivate {
constructor(private reflector: Reflector) {}
canActivate(context: ExecutionContext): boolean {
const isPublic = this.reflector.getAllAndOverride<boolean>(IS_PUBLIC_KEY, [
context.getHandler(),
context.getClass(),
]);
if (isPublic) {
return true;
}
// JWT検証などの認証ロジック
const request = context.switchToHttp().getRequest();
return this.validateRequest(request);
}
private validateRequest(request: any): boolean {
// 実際のJWT検証ロジック
return !!request.user;
}
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
// src/auth/auth.controller.ts
import { Controller, Post, Body } from '@nestjs/common';
import { Public } from './decorators/public.decorator';
@Controller('auth')
export class AuthController {
@Public()
@Post('login')
login(@Body() loginDto: LoginDto) {
// ログイン処理
}
@Public()
@Post('register')
register(@Body() registerDto: RegisterDto) {
// 登録処理
}
}
|
階層的なロール設計パターン#
実務では、ロールに階層構造を持たせることで、管理を簡素化できます。例えば、AdminはModeratorの権限をすべて持ち、ModeratorはUserの権限をすべて持つという設計です。
ロール階層の定義#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
// src/auth/enums/role.enum.ts
export enum Role {
User = 'user',
Moderator = 'moderator',
Admin = 'admin',
SuperAdmin = 'super_admin',
}
// ロールの階層レベルを定義
export const ROLE_HIERARCHY: Record<Role, number> = {
[Role.User]: 1,
[Role.Moderator]: 2,
[Role.Admin]: 3,
[Role.SuperAdmin]: 4,
};
|
階層対応のRolesGuard#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
|
// src/auth/guards/roles.guard.ts
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { Role, ROLE_HIERARCHY } from '../enums/role.enum';
import { Roles } from '../decorators/roles.decorator';
@Injectable()
export class RolesGuard implements CanActivate {
constructor(private reflector: Reflector) {}
canActivate(context: ExecutionContext): boolean {
const requiredRoles = this.reflector.getAllAndOverride<Role[]>(Roles, [
context.getHandler(),
context.getClass(),
]);
if (!requiredRoles || requiredRoles.length === 0) {
return true;
}
const { user } = context.switchToHttp().getRequest();
if (!user || !user.roles) {
return false;
}
// ユーザーの最高ロールレベルを取得
const userMaxLevel = this.getMaxRoleLevel(user.roles);
// 必要なロールの最低レベルを取得
const requiredMinLevel = this.getMinRoleLevel(requiredRoles);
// ユーザーのロールレベルが必要レベル以上であれば許可
return userMaxLevel >= requiredMinLevel;
}
private getMaxRoleLevel(roles: Role[]): number {
return Math.max(...roles.map((role) => ROLE_HIERARCHY[role] || 0));
}
private getMinRoleLevel(roles: Role[]): number {
return Math.min(...roles.map((role) => ROLE_HIERARCHY[role] || 0));
}
}
|
この実装により、@Roles(Role.Moderator)と指定すれば、Moderator、Admin、SuperAdminのいずれかを持つユーザーがアクセスできます。
flowchart TD
subgraph "階層的アクセス制御"
SA[SuperAdmin<br/>Level 4] --> A[Admin<br/>Level 3]
A --> M[Moderator<br/>Level 2]
M --> U[User<br/>Level 1]
end
subgraph "アクセス可能なリソース"
R1["@Roles(User)<br/>Level 1+"]
R2["@Roles(Moderator)<br/>Level 2+"]
R3["@Roles(Admin)<br/>Level 3+"]
R4["@Roles(SuperAdmin)<br/>Level 4"]
end
SA -.-> R1
SA -.-> R2
SA -.-> R3
SA -.-> R4
A -.-> R1
A -.-> R2
A -.-> R3
M -.-> R1
M -.-> R2
U -.-> R1
style SA fill:#E91E63,color:#fff
style A fill:#9C27B0,color:#fff
style M fill:#3F51B5,color:#fff
style U fill:#00BCD4,color:#fff使用例#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
@Controller('articles')
@UseGuards(AuthGuard, RolesGuard)
export class ArticlesController {
// すべての認証済みユーザーがアクセス可能
@Get()
@Roles(Role.User)
findAll() {
return this.articlesService.findAll();
}
// Moderator以上がアクセス可能
@Post()
@Roles(Role.Moderator)
create(@Body() dto: CreateArticleDto) {
return this.articlesService.create(dto);
}
// Admin以上がアクセス可能
@Delete(':id')
@Roles(Role.Admin)
remove(@Param('id') id: string) {
return this.articlesService.remove(+id);
}
// SuperAdminのみアクセス可能
@Post('bulk-delete')
@Roles(Role.SuperAdmin)
bulkDelete(@Body() ids: number[]) {
return this.articlesService.bulkDelete(ids);
}
}
|
カスタムエラーレスポンスの実装#
デフォルトでは、Guardがfalseを返すとForbiddenExceptionがスローされ、以下のレスポンスが返されます。
1
2
3
4
5
|
{
"statusCode": 403,
"message": "Forbidden resource",
"error": "Forbidden"
}
|
より詳細なエラーメッセージを返したい場合は、カスタム例外をスローします。
カスタム例外の作成#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
// src/common/exceptions/insufficient-role.exception.ts
import { ForbiddenException } from '@nestjs/common';
import { Role } from '../../auth/enums/role.enum';
export class InsufficientRoleException extends ForbiddenException {
constructor(requiredRoles: Role[], userRoles: Role[]) {
super({
statusCode: 403,
error: 'Forbidden',
message: 'アクセス権限が不足しています',
requiredRoles,
userRoles,
});
}
}
|
RolesGuardでのカスタム例外使用#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
|
// src/auth/guards/roles.guard.ts
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { Role, ROLE_HIERARCHY } from '../enums/role.enum';
import { Roles } from '../decorators/roles.decorator';
import { InsufficientRoleException } from '../../common/exceptions/insufficient-role.exception';
@Injectable()
export class RolesGuard implements CanActivate {
constructor(private reflector: Reflector) {}
canActivate(context: ExecutionContext): boolean {
const requiredRoles = this.reflector.getAllAndOverride<Role[]>(Roles, [
context.getHandler(),
context.getClass(),
]);
if (!requiredRoles || requiredRoles.length === 0) {
return true;
}
const { user } = context.switchToHttp().getRequest();
if (!user || !user.roles) {
throw new InsufficientRoleException(requiredRoles, []);
}
const userMaxLevel = this.getMaxRoleLevel(user.roles);
const requiredMinLevel = this.getMinRoleLevel(requiredRoles);
if (userMaxLevel < requiredMinLevel) {
throw new InsufficientRoleException(requiredRoles, user.roles);
}
return true;
}
private getMaxRoleLevel(roles: Role[]): number {
return Math.max(...roles.map((role) => ROLE_HIERARCHY[role] || 0));
}
private getMinRoleLevel(roles: Role[]): number {
return Math.min(...roles.map((role) => ROLE_HIERARCHY[role] || 0));
}
}
|
実装のテスト#
RBACの動作を検証するためのテストコードを作成します。
RolesGuardのユニットテスト#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
|
// src/auth/guards/roles.guard.spec.ts
import { Test, TestingModule } from '@nestjs/testing';
import { Reflector } from '@nestjs/core';
import { ExecutionContext } from '@nestjs/common';
import { RolesGuard } from './roles.guard';
import { Role } from '../enums/role.enum';
describe('RolesGuard', () => {
let guard: RolesGuard;
let reflector: Reflector;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [RolesGuard, Reflector],
}).compile();
guard = module.get<RolesGuard>(RolesGuard);
reflector = module.get<Reflector>(Reflector);
});
const mockExecutionContext = (user: any): ExecutionContext => {
return {
switchToHttp: () => ({
getRequest: () => ({ user }),
}),
getHandler: () => jest.fn(),
getClass: () => jest.fn(),
} as unknown as ExecutionContext;
};
it('ロールが設定されていない場合はtrueを返す', () => {
jest.spyOn(reflector, 'getAllAndOverride').mockReturnValue(null);
const context = mockExecutionContext({ roles: [Role.User] });
expect(guard.canActivate(context)).toBe(true);
});
it('ユーザーが必要なロールを持っている場合はtrueを返す', () => {
jest.spyOn(reflector, 'getAllAndOverride').mockReturnValue([Role.Admin]);
const context = mockExecutionContext({ roles: [Role.Admin] });
expect(guard.canActivate(context)).toBe(true);
});
it('上位ロールは下位ロールの権限にアクセスできる', () => {
jest.spyOn(reflector, 'getAllAndOverride').mockReturnValue([Role.Moderator]);
const context = mockExecutionContext({ roles: [Role.Admin] });
expect(guard.canActivate(context)).toBe(true);
});
it('ユーザーが必要なロールを持っていない場合は例外をスロー', () => {
jest.spyOn(reflector, 'getAllAndOverride').mockReturnValue([Role.Admin]);
const context = mockExecutionContext({ roles: [Role.User] });
expect(() => guard.canActivate(context)).toThrow();
});
it('ユーザー情報がない場合は例外をスロー', () => {
jest.spyOn(reflector, 'getAllAndOverride').mockReturnValue([Role.User]);
const context = mockExecutionContext(null);
expect(() => guard.canActivate(context)).toThrow();
});
});
|
テストの実行#
1
|
npm run test -- --testPathPattern=roles.guard
|
期待される出力は以下のとおりです。
1
2
3
4
5
6
7
|
PASS src/auth/guards/roles.guard.spec.ts
RolesGuard
✓ ロールが設定されていない場合はtrueを返す (3 ms)
✓ ユーザーが必要なロールを持っている場合はtrueを返す (1 ms)
✓ 上位ロールは下位ロールの権限にアクセスできる (1 ms)
✓ ユーザーが必要なロールを持っていない場合は例外をスロー (2 ms)
✓ ユーザー情報がない場合は例外をスロー (1 ms)
|
完成したコードの全体構成#
最終的なディレクトリ構成は以下のようになります。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
src/
├── app.module.ts
├── auth/
│ ├── auth.module.ts
│ ├── decorators/
│ │ ├── public.decorator.ts
│ │ └── roles.decorator.ts
│ ├── enums/
│ │ └── role.enum.ts
│ └── guards/
│ ├── auth.guard.ts
│ ├── roles.guard.ts
│ └── roles.guard.spec.ts
├── common/
│ └── exceptions/
│ └── insufficient-role.exception.ts
└── articles/
├── articles.module.ts
├── articles.controller.ts
└── articles.service.ts
|
まとめ#
本記事では、NestJSにおけるRBAC(ロールベースアクセス制御)の実装方法を解説しました。
| 項目 |
内容 |
| Role Enum |
システムで使用するロールを型安全に定義 |
| @Roles()デコレータ |
Reflector.createDecorator()で型安全なカスタムデコレータを作成 |
| RolesGuard |
Reflectorでメタデータを取得しユーザーロールと照合 |
| 階層的ロール |
ロールレベルを数値で管理し上位ロールが下位権限にアクセス可能 |
| グローバル登録 |
APP_GUARDで認証・認可Guardを一括適用 |
RBACを正しく実装することで、セキュアで保守性の高いAPIを構築できます。さらに高度な認可制御が必要な場合は、CASLライブラリを使用した属性ベースアクセス制御(ABAC)の導入も検討してください。
参考リンク#