Guards | NestJS 【翻訳】

## NestJS公式ドキュメント翻訳

[NestJS公式ドキュメント翻訳](https://qiita.com/mana-vv/items/a658051a5183183c3e50)

## 原文

[Guards| NestJS - A progressive Node.js web framework](https://docs.nestjs.com/guards)

## ガード

ガードは`@Injectable()`デコレータが付けられたクラスです。ガードは`CanActivate`インターフェースを実装する必要があります。

<img width="839" alt="スクリーンショット 2019-11-09 18.16.34.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/499074/69edf146-a5ec-7757-f62c-2a2635f8be0c.png">

ガードには**単一責任性**があります。実行時に存在する特定の条件（パーミッション、ロール、ACLなど）に応じて、リクエストがルートハンドラーによって処理されるかどうかを決定します。これは**認可(authorization)**と呼ばれます。認可（や通常はそれと連携する**認証(authentication)**）は通常は、従来のExpressアプリケーションの[ミドルウェア](https://qiita.com/mana-vv/items/6d6946085e360883e5ad)によって処理されてきました。ミドルウェアは認証に適した選択肢です。トークンの検証や`request`オブジェクトへのプロパティの添付などが、特定のルートコンテキスト（およびそのメタデータ）と強く結びついていないためです。

しかし、ミドルウェアはその性質上あまり賢くありません。`next()`関数を呼び出した後にどのハンドラーが実行されるかはわかりません。一方、ガードは`ExecutionContext`インスタンスにアクセスできるため、次に実行されるものを正確に把握することができます。例外フィルター、パイプ、インターセプターと同様に、リクエスト/レスポンスサイクルの正確なポイントに処理ロジックを挿入し、宣言的に実行できるように設計されています。これにより、コードをDRYで宣言的に保つことができます。

ガードは各ミドルウェアの後かつインターセプターやパイプの前に実行されます。

### 認可ガード

前述のように、特定のルートは呼び出し元（通常は特定の認証済みユーザー）が十分な権限を持っている場合にのみ使用できるため、認可はガードの優れたユースケースです。ここで作成する`AuthGuard`は認証されたユーザーを想定しています（トークンがリクエストヘッダーに添付されます）。トークンを抽出および検証し、抽出された情報を使用して、リクエストを続行できるかどうか判断します。

```typescript:auth.guard.ts
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { Observable } from 'rxjs';

@Injectable()
export class AuthGuard implements CanActivate {
  canActivate(
    context: ExecutionContext,
  ): boolean | Promise<boolean> | Observable<boolean> {
    const request = context.switchToHttp().getRequest();
    return validateRequest(request);
  }
}
```

`validateRequest()`関数内のロジックは必要に応じて単純にも複雑にもすることができます。このサンプルの重要なポイントはガードがリクエスト/レスポンスサイクルにどのように適合するかを示している点です。

すべてのガードは`canActivate()`関数を実装する必要があります。この関数は現在のリクエストが許可されているかどうかを示すブール値を返す必要があります。レスポンスを同期的または非同期的に（`Promise`または`Observable`を介して）返すことができます。 Nestは戻り値を使用して次のアクションを制御します。

- `true`を返した場合、リクエストが処理されます。
- `false`を返した場合、Nestはリクエストを拒否します。

### 実行コンテキスト

`canActivate()`関数は`ExecutionContext`インスタンスを引数に取ります。`ExecutionContext`は`ArgumentsHost`を継承します。`ArgumentsHost`は例外フィルターの章でも出てきました。上記のサンプルでは、​​先ほど使用した`ArgumentsHost`で定義された同じヘルパーメソッドを使用して、`Request`オブジェクトへの参照を取得しています。このトピックの詳細については、[例外フィルター](https://qiita.com/mana-vv/items/bd90964866b4197e4b6e)の章の**Arguments host**のセクションを参照してください。

`ArgumentsHost`を継承することにより、`ExecutionContext`は現在の実行プロセスに関する詳細情報を提供する新しいヘルパーメソッドも追加します。これらはコントローラ、メソッド、および実行コンテキストで幅広く機能する、より汎用的なガードを構築するのに役立ちます。`ExecutionContext`の追加メソッドは次のようになります。

```typescript
export interface ExecutionContext extends ArgumentsHost {
  getClass<T = any>(): Type<T>;
  getHandler(): Function;
}
```

`getHandler()`メソッドは呼び出されるハンドラーへの参照を返します。`getClass()`メソッドはこの特定のハンドラーが属する`Controller`クラスの型を返します。例えば、現在処理されているリクエストが`CatsController`の`create()`メソッド宛ての`POST`リクエストの場合、`getHandler()`は`create()`メソッドへの参照を返し、`getClass()`は（インスタンスではなく）`CatsController`**型**を返します。

### ロールベースの認証

特定のロールを持つユーザーのみにアクセスを許可するような機能的なガードを構築しましょう。基本的なガードのテンプレートから始め、次のセクションでそれを構築していきます。今のところ、すべてのリクエストを続行できます。

```typescript:roles.guard.ts
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { Observable } from 'rxjs';

@Injectable()
export class RolesGuard implements CanActivate {
  canActivate(
    context: ExecutionContext,
  ): boolean | Promise<boolean> | Observable<boolean> {
    return true;
  }
}
```

### ガードのバインディング

パイプや例外フィルターと同様に、ガードは**コントローラスコープ**、メソッドスコープ、グローバルスコープにすることができます。以下では、`@UseGuards()`デコレータを使用してコントローラスコープのガードを設定します。このデコレータは単一の引数、またはコンマ区切りのリストを引数に取ることができます。これにより、1つの宣言で適切な一連のガードを簡単に適用できます。

```typescript
@Controller('cats')
@UseGuards(RolesGuard)
export class CatsController {}
```

`@UseGuards()`デコレータは`@nestjs/common`パッケージからインポートされます。

上記では（インスタンスの代わりに）`RolesGuard`型を渡し、フレームワークにインスタンス化の責任を委ね、依存関係の注入を可能にしました。パイプおよび例外フィルターと同様に、インプレースなインスタンスを渡すこともできます。

```typescript
@Controller('cats')
@UseGuards(new RolesGuard())
export class CatsController {}
```

この構造はこのコントローラによって宣言されたすべてのハンドラーにガードを適用します。ガードが単一のメソッドにのみ適用されるようにしたい場合は、**メソッドレベル**で`@UseGuards()`デコレータを適用します。

グローバルガードを設定するにはNestアプリケーションインスタンスの`useGlobalGuards()`メソッドを使用します。

```typescript
const app = await NestFactory.create(AppModule);
app.useGlobalGuards(new RolesGuard());
```

ハイブリッドアプリの場合、`useGlobalGuards()`メソッドはゲートウェイとマイクロサービスのガードを設定しません。 「スタンダード」（非ハイブリッド）マイクロサービスアプリの場合、`useGlobalGuards()`はガードをグローバルにマウントします。

グローバルガードはすべてのコントローラとすべてのルートハンドラに対して、アプリケーション全体で使用されます。依存性注入に関しては、モジュールの外部で登録されたグローバルガード（上記の例では`useGlobalGuards()`）はモジュールのコンテキスト外で行われるため、依存性を注入できません。この問題を解決するには、次の構成を使用して、任意のモジュールから直接ガードを設定します。

```typescript:app.module.ts
import { Module } from '@nestjs/common';
import { APP_GUARD } from '@nestjs/core';

@Module({
  providers: [
    {
      provide: APP_GUARD,
      useClass: RolesGuard,
    },
  ],
})
export class AppModule {}
```

このアプローチを使用してガードの依存性注入を実行する場合、この構造が使用されるモジュールに関係なく、ガードは実際にはグローバルであることに注意してください。これはどこで行われるべきでしょうか？ガード（上記の例では`RolesGuard`）が定義されているモジュールを選んでください。また、カスタムプロバイダーの登録を処理する方法は`useClass`だけではありません。詳細は[こちら](https://docs.nestjs.com/fundamentals/custom-providers)をご覧ください。

### リフレクション

`RolesGuard`は機能していますが、まだあまり賢くはありません。最も重要なガード機能である**実行コンテキスト**をまだ利用していません。ロールや各ハンドラに許可されているロールについてはまだわかりません。例えば、`CatsController`にはルートごとに異なるパーミッションスキームを設定することができます。管理ユーザーのみが利用できるものもあれば、すべての人が利用できるものもあります。柔軟で再利用可能な方法で、どのようにロールをルートに一致させることができるでしょうか？

ここで**カスタムメタデータ**の出番です。 Nestは`@SetMetadata()`デコレータを介してルートハンドラーにカスタム**メタデータ**を添付する機能を提供します。このメタデータは不足している`role`データを提供し、ガードは処理を行うべきか決めます。それでは`@SetMetadata()`の使い方を見てみましょう。

```typescript:cats.controller.ts
@Post()
@SetMetadata('roles', ['admin'])
async create(@Body() createCatDto: CreateCatDto) {
  this.catsService.create(createCatDto);
}
```

`@SetMetadata()`デコレータは`@nestjs/common`パッケージからインポートされます。

上記の構成で、`roles`メタデータ（`roles`はキー、`['admin']`は特定の値）を`create()`メソッドに添付しました。これは機能しますが、ルートで`@SetMetadata()`を直接使用することはお勧めできません。代わりに、以下のように独自のデコレータを作成しましょう。

```typescript:roles.decorator.ts
import { SetMetadata } from '@nestjs/common';

export const Roles = (...roles: string[]) => SetMetadata('roles', roles);
```

このアプローチはより簡潔で読みやすく、強く型付けされています。カスタムの`@Roles()`デコレータができたので、それを使用して`create()`メソッドをデコレートできます。

```typescript:cats.controller.ts
@Post()
@Roles('admin')
async create(@Body() createCatDto: CreateCatDto) {
  this.catsService.create(createCatDto);
}
```

### まとめ

少し戻って、これを`RolesGuard`と結び付けましょう。現在、すべての場合で`true`を返すだけなのですべてのリクエストを続行してしまいます。**現在のユーザーに割り当てられているロール**と処理中の現在のルートに必要な実際のロールとの比較に基づいて、戻り値を条件付きにしましょう。ルートのロール（カスタムメタデータ）にアクセスするため、`Reflector`ヘルパークラスを使用します。これはフレームワークによって提供され、`@nestjs/core`パッケージから公開されます。

```typescript:roles.guard.ts
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { Observable } from 'rxjs';
import { Reflector } from '@nestjs/core';

@Injectable()
export class RolesGuard implements CanActivate {
  constructor(private readonly 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;
    const hasRole = () => user.roles.some((role) => roles.includes(role));
    return user && user.roles && hasRole();
  }
}
```

node.jsの世界では、承認されたユーザーを`request`オブジェクトにアタッチするのが一般的です。したがって、上記のサンプルコードでは、`request.user`にユーザーインスタンスと許可されたロールが含まれていると想定しています。一般的にアプリでは、おそらくカスタム認証ガード（またはミドルウェア）でその関連付けを行います。

`Reflector`クラスを使用すると、指定された**キー**でメタデータに簡単にアクセスできます（上記の場合、キーは`'roles'`です。`roles.decorator.ts`ファイルとそこで行われた`SetMetadata()`呼び出しを参照してください）。上記の例では、現在処理されているリクエストメソッドのメタデータを抽出するために`context.getHandler()`を渡しました。`getHandler()`はルートハンドラー関数への**参照**を提供することを忘れないでください。

**コントローラメタデータ**を抽出し、それを使用して現在のユーザーロールを決定することにより、このガードをより一般的にすることができます。コントローラのメタデータを抽出するには`context.getHandler()`ではなく`context.getClass()`を渡します。

```typescript
const roles = this.reflector.get<string[]>('roles', context.getClass());
```

権限が不十分なユーザーがエンドポイントを要求すると、Nestは自動的に次のレスポンスを返します。

```json
{
  "statusCode": 403,
  "message": "Forbidden resource"
}
```

裏でガードが`false`を返すときフレームワークは`ForbiddenException`をスローします。別のエラーレスポンスを返す場合は、独自の例外をスローする必要があります。例えば：

```typescript
throw new UnauthorizedException();
```

ガードによってスローされたすべての例外は、[例外レイヤー](https://qiita.com/mana-vv/items/bd90964866b4197e4b6e)（グローバル例外フィルターおよび現在のコンテキストに適用されるすべての例外フィルター）によって処理されます。