NestJSでDigest認証

NestJSでDigest認証のGuardを追加します。

:::note warn
先に断っておくと、認証処理の本質を理解しておらず「よくわからないけどなんかできたっぽい？」というかんじなのでそのつもりでご覧いただきたいのと、正しいやり方があればぜひ教えて頂きたいです。
:::

## 必要なパッケージをインストール

```
$ npm install --save @nestjs/passport passport passport-http
$ npm install --save-dev @types/passport-http
```

## Digest認証のStrategyを作成
ユーザ名：```"admin"```、パスワード：```"pass"```で認証をチェックする。
この```validate()```がよくわかっていないところ。
ソースコードを追いながら試行錯誤して、よくわからないけどこうするとちゃんとチェックしてくれるようになったっぽいというかんじです。
```digest.strategy.ts
import {DigestStrategy as Strategy} from 'passport-http';
import {PassportStrategy} from '@nestjs/passport';
import {Injectable, UnauthorizedException} from '@nestjs/common';

@Injectable()
export class DigestStrategy extends PassportStrategy(Strategy) {
  constructor() {
    super();
  }

  validate(): string[] {
    const auth: string[] = ["admin", "pass"]; 
    return auth;
  }
}
```
## Guardを作成
```
$ nest g guard digest-auth
```

```digest-auth```フォルダが作成されその配下に```digest-auth.guard.ts```ファイルと```digest-auth.guard.spec.ts```ファイルが作成されます。
```digest-auth.guard.ts```ファイルを下記のように記述します。
```digest-auth.guard.ts
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
import { Observable } from 'rxjs';

@Injectable()
export class DigestAuthGuard extends AuthGuard('digest') implements CanActivate {
  canActivate(
    context: ExecutionContext,
  ): boolean | Promise<boolean> | Observable<boolean> {
    return super.canActivate(context);
  }
}
```
## モジュールで定義
```PassportModule```を```import```して、```DigestStrategy```を```providers```に追加します。
```app.module.ts
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { PassportModule } from '@nestjs/passport';
import { DigestStrategy } from './common/guards/digest-auth/digest.strategy';

@Module({
  imports: [PassportModule],
  controllers: [AppController],
  providers: [AppService, DigestStrategy],
})
export class AppModule {}

```

## ブラウザからアクセス時、Digest認証のユーザ、パスワードを入力するダイアログを表示するようにする（ExceptionFilterを追加する）
下記ファイルを作成する。
```auth-exception.filter.ts
import { ExceptionFilter, Catch, ArgumentsHost, HttpStatus } from '@nestjs/common';
import { HttpException } from '@nestjs/common';

@Catch(HttpException)
export class AuthExceptionFilter implements ExceptionFilter {
  catch(exception: HttpException, host: ArgumentsHost) {
    const ctx = host.switchToHttp();
    const response = ctx.getResponse();
    const status = exception.getStatus();

    if(HttpStatus.UNAUTHORIZED == status){
      response.set('WWW-Authenticate', 
      'Digest realm="aaa",qop="auth,auth-int",nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093",opaque="5ccc069c403ebaf9f0171e9517f40e41",algorithm="MD5"');
    }
    response
      .status(status)
      .json({
        statusCode: status,
      });
  }
}
```
認証に失敗した例外をキャッチしてダイアログを出すようにしているのだと思いますが、この処理に関してはほぼ何もわかっていません。
Basic認証でダイアログを表示する方法を書いていて下さった参考サイト(後述)をそのまま真似て、パラメータをDigest認証用に書き換えただけです。そのDigest認証用のパラメータもコピペで内容は理解していません。
ただ```algorithm="MD5"```だけはソースコードを追って、あった方が良さそうだったので追加してみました。

## 作成したGuardとFilterをグローバルで使用
今回は全てのアクセスに有効となるよう、```main.ts```にグローバルで設定しました。

```main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { ExpressAdapter } from '@nestjs/platform-express';
import { AuthExceptionFilter } from './common/filters/auth-exception.filter';
import { DigestAuthGuard } from './common/guards/digest-auth/digest-auth.guard';

async function bootstrap() {
  const server = express();
  const app = await NestFactory.create(AppModule, new ExpressAdapter(server));
  app.useGlobalFilters(new AuthExceptionFilter)
  app.useGlobalGuards(new DigestAuthGuard);
  await app.listen(3000, '0.0.0.0');
}
bootstrap();
```

これでブラウザからアクセスするとユーザ、パスワードを入力するダイアログが表示され、ユーザ名に```"admin"```、パスワードに```"pass"```を入力すると認証されます。（一度認証に成功すると以降のアクセスからは表示されません）
もし間違っていたら401エラーが返ってきます。（その他何か実装をミスっていたりすると500エラーが返ってくると思います）

ちなみにグローバルでなく、エンドポイント単位、コントローラ単位、モジュール単位でも認証処理(ガード)を実行できます。
### エンドポイント単位でガードをかける場合
```app.controller.ts
@Controller()
export class AppController {
  constructor(private readonly appService: AppService) {}

  @Get()
  @UseGuards(DigestAuthGuard)
  getHello(): string {
    return this.appService.getHello();
  }
```
### コントローラ単位でガードをかける場合
```app.controller.ts
@UseGuards(DigestAuthGuard)
@Controller()
export class AppController {
  constructor(private readonly appService: AppService) {}

  @Get()
  getHello(): string {
    return this.appService.getHello();
  }
```
### モジュール単位でガードをかける場合
```app.module.ts
@Module({
  imports: [PassportModule],
  controllers: [AppController],
  providers: [AppService, DigestStrategy, {provide: APP_GUARD, useClass: DigestAuthGuard}],
})
export class AppModule {}
```
```APP_GUARD```は全てのエンドポイントに対してガードを適用するための定数。


## 参考サイト

https://qiita.com/11-1010-100/items/263d7f3e76ca29b24485

https://zenn.dev/maronn/articles/strategies-in-nestjs

https://msitter29.hatenablog.com/entry/2019/03/13/191234

https://unarist.hatenablog.com/entry/2015/09/25/013142

https://zenn.dev/maronn/articles/authenticate-document-in-nestjs