前回
実行コンテクスト
canActivate()
メソッドは、ExecutionContext
インスタンスという1つの引数をとります。
ExecutionContext
は、ArgumentsHost
から継承します。
ArgumentsHost
を拡張することで、ExecutionContext
は、現在の実行プロセスに関する追加の詳細を提供する新しいヘルパメソッドも追加します。
ロールベースの認証
特定の役割を持つユーザにのみアクセスを許可する、より機能的なガードを作成してみましょう。
$ nest g guard guard/roles
以下のファイルが作成されました。
src/guard/roles/roles.guard.ts
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common'; import { Observable } from 'rxjs'; @Injectable() export class RolesGuard implements CanActivate { canActivate( context: ExecutionContext, ): boolean | Promise<boolean> | Observable<boolean> { return true; } }
src/guard/roles/roles.guard.spec.ts
import { RolesGuard } from './roles.guard'; describe('RolesGuard', () => { it('should be defined', () => { expect(new RolesGuard()).toBeDefined(); }); });
コントローラにガードをバインドします。
src/cats/cats.controller.ts
import { Body, Controller, Get, Post, UseGuards } from '@nestjs/common'; import { CreateCatDto } from 'src/dto/cat/cat.dto'; import { Cat } from './cats.interface'; import { CatsService } from './cats.service'; import { ValidationPipe } from '../pipe/validation/validation.pipe'; import { RolesGuard } from 'src/guard/roles/roles.guard'; @Controller('cats') @UseGuards(RolesGuard) export class CatsController { constructor(private catsService: CatsService) {} @Post() create(@Body(new ValidationPipe()) createCatDto: CreateCatDto) { this.catsService.create(createCatDto); } @Get() findAll(): Cat[] { return this.catsService.findAll(); } }
@UseGuards(RolesGuard)
を追加しました。
この構成は、コントローラによって宣言された全てのハンドラにガードをアタッチしています。
また、この状態では、また各ハンドラにどのロールが許可されているかわかりません。
そのため、カスタムメタデータを使用します。
@SetMetadata()
デコレータを使用して、カスタムメタデータをルートハンドラにアタッチすることができます。
src/cats/cats.controller.ts
import { Body, Controller, Get, Post, SetMetadata, UseGuards } from '@nestjs/common'; import { CreateCatDto } from 'src/dto/cat/cat.dto'; import { Cat } from './cats.interface'; import { CatsService } from './cats.service'; import { ValidationPipe } from '../pipe/validation/validation.pipe'; import { RolesGuard } from 'src/guard/roles/roles.guard'; @Controller('cats') @UseGuards(RolesGuard) export class CatsController { constructor(private catsService: CatsService) {} @Post() @SetMetadata('roles', ['admin']) create(@Body(new ValidationPipe()) createCatDto: CreateCatDto) { this.catsService.create(createCatDto); } @Get() findAll(): Cat[] { return this.catsService.findAll(); } }
@SetMetadata('roles', ['admin'])
を設定しました。
ただし、この記述方法は動作しますが、推奨されません。
代わりに、カスタムデコレータを作成します。
$ nest g decorator decorator/roles
以下のファイルが作成されました。
src/decorator/roles/roles.decorator.ts
import { SetMetadata } from '@nestjs/common'; export const Roles = (...roles: string[]) => SetMetadata('roles', roles);
カスタムデコレータが作成できたので、コントローラにバインドします。
src/cats/cats.controller.ts
import { Body, Controller, Get, Post, UseGuards } from "@nestjs/common"; import { CreateCatDto } from "src/dto/cat/cat.dto"; import { Cat } from "./cats.interface"; import { CatsService } from "./cats.service"; import { ValidationPipe } from "../pipe/validation/validation.pipe"; import { Roles } from "src/decorator/roles/roles.decorator"; import { RolesGuard } from "src/guard/roles/roles.guard"; @Controller("cats") @UseGuards(RolesGuard) export class CatsController { constructor(private catsService: CatsService) {} @Post() @Roles("admin") create(@Body(new ValidationPipe()) createCatDto: CreateCatDto) { this.catsService.create(createCatDto); } @Get() findAll(): Cat[] { return this.catsService.findAll(); } }
@Roles('admin')
を設置しました。
少しシンプルになりました。
最後に、ガードを修正します。
src/guard/roles/roles.guard.ts
import { CanActivate, ExecutionContext, Injectable } from "@nestjs/common"; import { Reflector } from "@nestjs/core"; @Injectable() export class RolesGuard implements CanActivate { constructor(private reflector: Reflector) {} canActivate(context: ExecutionContext): boolean { const roles = this.reflector.get<string[]>("roles", context.getHandler()); if (!roles) { return true; } const request = context.switchToHttp().getRequest(); const user = request.user; return matchRoles(roles, user.roles); } } function matchRoles(roles: string[], roles1: any): boolean { throw new Error("Function not implemented."); }
カスタムメタデータにアクセスするために、Reflector
ヘルパークラスを使用しています。
matchRoles
メソッドは、必要に応じて変更することができます。
まとめ
ここまでで、
- 実行コンテクストについて
- ロールベースの認証
を学びました。
次回は、インターセプタについて掘り下げようと思います。
コード
今回のコードは、以下に格納しました。