🔖

NestJSでリクエストのCookieを利用する方法

2023/01/14に公開

NestJSの学習をしていて、あるエラーに出会いました。
デバッグをして調べたら、どうやらリクエストのCookieを取得しようとしているところでCookieが上手く取得できていなかったことが原因のようです。

学習コンテンツ

https://www.udemy.com/course/nestjs-nextjs-restapi-react/?src=sac&kw=NestJS

GitHub

https://github.com/toranoko114/typescript-nest-backend

コード

main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { ValidationPipe } from '@nestjs/common';
import * as cookieParser from 'cookie-parser';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.useGlobalPipes(new ValidationPipe({ whitelist: true }));
  app.enableCors({
    credentials: true,
    origin: ['http://localhost:3000'],
  });
  await app.listen(3005);
}
bootstrap();

jwt.strategy.ts
import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { PassportStrategy } from '@nestjs/passport';
import { ExtractJwt, Strategy } from 'passport-jwt';
import { PrismaService } from '../../prisma/prisma.service';

@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy, 'jwt') {
  constructor(
    private readonly config: ConfigService,
    private readonly prisma: PrismaService,
  ) {
    super({
      jwtFromRequest: ExtractJwt.fromExtractors([
        (req) => {
          let jwt = null;
          if (req && req.cookies) {
            jwt = req.cookies['access_token'];
          }
          return jwt;
        },
      ]),
      ignoreExpiration: false,
      secretOrKey: config.get('JWT_SECRET'),
    });
  }

  async validate(payload: { sub: number; email: string }) {
    const user = await this.prisma.user.findUnique({
      where: {
        id: payload.sub,
      },
    });
    delete user.hashedPassword;
    return user;
  }
}
UserController.ts
import { Controller, Get, Req, UseGuards } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
import { User } from '@prisma/client';
import { Request } from 'express';
import { UserService } from './user.service';

@UseGuards(AuthGuard('jwt'))
@Controller('user')
export class UserController {
  constructor(private readonly userService: UserService) {}

  @Get()
  getLoginUser(@Req() req: Request): Omit<User, 'hashedPassword'> {
    return req.user;
  }
}
}

事象

今回は、Postmanで http://localhost:3005/user を実行したときに、想定動作にならず、下記のレスポンスとなりました。

{
    "statusCode": 401,
    "message": "Unauthorized"
}
想定動作

ログインしているユーザ情報がレスポンスされる

{
    "id": 1,
    "createdAt": "2023-01-13T09:52:30.509Z",
    "updatedAt": "2023-01-14T04:58:35.679Z",
    "email": "xxx@gmail.com",
    "nickName": "とらのこ"
}

原因

デバッグをして調査したところ、jwt.strategy.tsreq.cookiesがNullで存在しないことが判明しました。

jwt.strategy.ts (一部抜粋)
super({
  jwtFromRequest: ExtractJwt.fromExtractors([
    (req) => {
      let jwt = null;
      if (req && req.cookies) {
        jwt = req.cookies['access_token']; ⇦ req.cookiesがない!!
      }
      return jwt;
    },
  ]),
  ignoreExpiration: false,
  secretOrKey: config.get('JWT_SECRET'),
});

そこで、NestJSでリクエストのCookieを取得できる方法を公式ドキュメントで確認することにしました。

NestJSでリクエストのCookieを利用する方法

https://docs.nestjs.com/techniques/cookies
まずは下記のとおり、必要なパッケージをインストールする。

$ npm i cookie-parser
$ npm i -D @types/cookie-parser

その後、ルートモジュールで下記のように定義する。

main.ts
import * as cookieParser from 'cookie-parser';
// somewhere in your initialization file
app.use(cookieParser());

これだけ・・??

パッケージのインストールはした覚えあるし、実際packege.jsonを見てもちゃんとインストールされていました。

まさかと思い、main.tsを見に行ったら、なんとapp.use(cookieParser());の記載が抜けていました🥹 パッケージのインポートまでしているのに・・

非常に初歩的なミスでしたが、main.tsapp.use(cookieParser());を追加したら、想定動作になりました!

修正後のコード

jwt.strategy.tsUserController.tsは修正前と同一のため割愛します。

main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { ValidationPipe } from '@nestjs/common';
import * as cookieParser from 'cookie-parser';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.use(cookieParser()); ⇦ 追加!!
  app.useGlobalPipes(new ValidationPipe({ whitelist: true }));
  app.enableCors({
    credentials: true,
    origin: ['http://localhost:3000'],
  });
  await app.listen(3005);
}
bootstrap();

以上です。

Discussion