Nest.jsのGuardでCognito UserPoolsのユーザー認証をする

Nest.jsのGuardを使うことで、APIへのアクセス許可を管理できます。

AWSのCognito UserPoolsでユーザーを管理していることが多いので、そのためのGuardを簡単に作ってみました。

コード

実装は2ファイル + アルファです。Nest CLIでサクッと作りましょう。

% nest g s cognito
CREATE /src/cognito/cognito.service.spec.ts (467 bytes)
CREATE /src/cognito/cognito.service.ts (91 bytes)
UPDATE /src/app.module.ts (1046 bytes)

% nest g gu authorizer
CREATE /src/authorizer.guard.spec.ts (184 bytes)
CREATE /src/authorizer.guard.ts (305 bytes)

ファイル1: AWS SDKでCognitoにアクセスするサービス

まずは/src/cognito/cognito.service.tsに実装を書きます。

import { Injectable } from '@nestjs/common';
import { CognitoIdentityServiceProvider } from 'aws-sdk';
import { GetUserResponse } from 'aws-sdk/clients/cognitoidentityserviceprovider';

@Injectable()
export class CognitoService {
    private client: CognitoIdentityServiceProvider
    protected user: GetUserResponse
    constructor() {
        this.client = new CognitoIdentityServiceProvider()
    }
    public async getUserByToken(token: string): Promise<GetUserResponse> {
        this.user = await this.client.getUser({
            AccessToken: token
        }).promise()
        return this.user
    }
    public loadCurrentUser(): GetUserResponse {
        return this.user
    }
}

getUserByTokenでリクエストヘッダーのtokenを使ってユーザー取得を試行します。Controllerなどでユーザー情報を取得する場合はloadCurrentUserを使います。

ファイル2: Gurdでのアクセス管理

もう1つのファイル、/src/authorizer.guard.tsが実際にアクセスの制御を行うファイルです。

import { CanActivate, ExecutionContext, Injectable, UnauthorizedException } from '@nestjs/common';
import { Request } from 'express';
import { CognitoService } from './cognito.service';

@Injectable()
export class AuthorizerGuard implements CanActivate {
  constructor(private readonly cognito: CognitoService) {}

  async canActivate(
    context: ExecutionContext,
  ): Promise<boolean> {
    const request = context.switchToHttp().getRequest<Request>();
    const { authorization } = request.headers;
    await this.authorizeByCognito(authorization)
    return true;
  }

  public async authorizeByCognito(authorizationToken?: string):Promise<void> {
    if (!authorizationToken) throw new UnauthorizedException(`Authorization header is required.`)
    try {
      await this.cognito.getUserByToken(authorizationToken)
    } catch (e) {
      if (e.name === 'NotAuthorizedException') throw new UnauthorizedException()
      throw e
    }
  }
}

リクエストヘッダーにauthorizationがない場合や、Conitoからユーザーが取得できなかった場合にUnauthorizedExceptionを投げるようにしています。

エラーが投げられなければ問題なしとして、最後にreturn trueする実装としました。

authorizeByCognitoメソッドをわざわざ作らなくても問題はないのですが、canActivateメソッドに対してテストを書くのがちょっと煩雑だったために分割しています。

APIへの設定とユーザーの取得

あとはAPIへの設定と、その後ユーザーの情報を取得する部分です。

import { Controller, Get, UseGuards } from '@nestjs/common';
import { AuthorizerGuard } from './aws/cognito/authorizer.guard';
import { CognitoService } from './aws/cognito/cognito.service';

@Controller()
@UseGuards(AuthorizerGuard)
export class AppController {
  constructor(private readonly cognito: CognitoService) {}

  @Get()
  getHello(): string {
    return `Hello ${this.cognito.loadCurrentUser().Username}`;
  }
}

useGuardsデコレーターで作成したGuardを設定し、CognitoServiceを使ってユーザー名などをロードしています。

Comment