0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

NestJSでDigest認証

Posted at

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

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

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

$ 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);
  }
}

モジュールで定義

PassportModuleimportして、DigestStrategyprovidersに追加します。

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は全てのエンドポイントに対してガードを適用するための定数。

参考サイト

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?