Nest.jsでGraphQL + Passportを使って認証処理を作る。

Nest.jsで認証のライブラリであるPassportを使用する方法は[公式ドキュメント](https://docs.nestjs.com/security/authentication#implementing-passport-strategies)に載っており、GraphQLでの実装も参考程度に乗っています。
しかし、GraphQLでの実装はあくまでも参考程度なので、これだけでは動作せることができません。
今回は実際にGraphQLでusernameとpasswordでの認証ができるところまで実装してみます。

# 1. ベースプロジェクトの作成
下記コマンドでNest.jsのプロジェクトを作成します。

```terminal
$ npm i -g @nestjs/cli
$ nest new passport-sample
$ cd passport-sample
```

プロジェクト作成後、[Authentication requirements](https://docs.nestjs.com/security/authentication#authentication-requirements)の手順に沿って、必要なライブラリをインストールします。

# 2. モジュールの作成
[Implementing Passport strategies](https://docs.nestjs.com/security/authentication#implementing-passport-strategies) に沿って、passportに必要なサンプルのモジュールの作成と各種ロジックをサンプル実装をします。
ここでは下記のファイルが作成されます。

- users/users.service.ts
- users/users.module.ts
- auth/auth.service.ts
- auth/auth.module.ts


# 3. LocalStrategyの作成
認証のためのバリデーションを実行するLocalStrategyを[Implementing Passport local] (https://docs.nestjs.com/security/authentication#implementing-passport-local)に沿って実装します。
LocalStrategyは下記の様になります。

```typescript:auth/local.strategy.ts
import { Strategy } from 'passport-local';
import { PassportStrategy } from '@nestjs/passport';
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { AuthService } from './auth.service';

@Injectable()
export class LocalStrategy extends PassportStrategy(Strategy) {
  constructor(private authService: AuthService) {
    super();
  }

  async validate(username: string, password: string): Promise<any> {
    const user = await this.authService.validateUser(username, password);
    if (!user) {
      throw new UnauthorizedException();
    }
    return user;
  }
}
```

手順に沿って`AuthModule`も更新します。


# 4. LocalAuthGuardの作成
公式ドキュメントでは、LocalAuthGuardは下記の様になっています。

```typescript:auth/local-auth.guard.ts
import { Injectable } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';

@Injectable()
export class LocalAuthGuard extends AuthGuard('local') {}
```

しかし、この実装だとGraphQLから渡されるパラメーターを正しくLocalStrategyに渡せていないため、認証に失敗してしまいます。
そのため、GraphQLでこのLocalAuthGuardを実装するには、以下のように`getRequest`メソッドでパラメーターをマッピングしてあげる必要があります。

```typescript:auth/local-auth.guard.ts
import { Injectable, ExecutionContext } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
import { GqlExecutionContext } from '@nestjs/graphql';

@Injectable()
export class LocalAuthGuard extends AuthGuard('local') {
  getRequest(context: ExecutionContext) {
    const ctx = GqlExecutionContext.create(context);
    const gqlReq = ctx.getContext().req;

    if (gqlReq) {
      gqlReq.body = ctx.getArgs();
      return gqlReq;
    }
    return context.switchToHttp().getRequest();
  }
}
```

# 5. MutaionにLocalAuthGuardをセットする
まずはUser情報を返却するためのModelを定義します。

```typescript:users/models/user.model.ts

import { Field, Int, ObjectType } from '@nestjs/graphql';

@ObjectType()
export class User {
  @Field(type => Int)
  userId: number;

  @Field()
  username: string;
}
```

`app.resolver.ts`にloginのMutationを作成し、LocalAuthGuardをセットします。

```typescript:app.resolver.ts
import { UseGuards, UseInterceptors } from '@nestjs/common';
import { Resolver, Mutation, Args } from '@nestjs/graphql';

import { LocalAuthGuard } from './auth/local-auth.guard';
import { User } from './users/models/user.model';

@Resolver()
export class AppResolver {
  @UseGuards(LocalAuthGuard)
  @Mutation(() => User)
  async login(
    @Request() req,
    @Args({ name: 'username', type: () => Int }) _: number,
    @Args({ name: 'password' }) _: string
  ): Promise<User> {
    return req.user
  }
}
```

これで、GraphQlでPassportを使った認証処理が動く様になります。