NestJSでCookieを利用する

## これはなに？

業務でCookieを用いた実装することがあったので備忘録として書きました。

記事の下の方ではSet-Cookieの各属性について説明したのでCookieを調べている方の一助になれば幸いです。

## この記事のゴール

以下の理解が得られることをゴールとします

- NestJSでリクエストからCookieを取得できること
- NestJSでレスポンスにCookieを詰められること
- Set-Cookieの各属性について理解できること

## 手順
### 0. NestJSの導入

導入がまだの方は以前書いたこちらの記事を参考にしてもらえると理解が早いと思います。

https://qiita.com/mu-suke08/items/982bd5610deb8f801a4e

### 1. cookie-parserの導入
今回は公式でも書かれている`cookie-parser`を利用します。

2022年1月時点で最新のものを利用しています。

パッケージ名 | バージョン
:-: | :-: 
[cookie-parser](https://www.npmjs.com/package/cookie-parser) | v1.4.6
[@types/cookie-parser](https://www.npmjs.com/package/@types/cookie-parser) | v1.4.2

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

### 2. ルートモジュールに適用する
ルートモジュールにcookieを利用できるように記載します。

公式では以下のように書かれています。

```typescript:NestJS公式ドキュメント
import * as cookieParser from 'cookie-parser';
// somewhere in your initialization file
app.use(cookieParser());
```

今回NestJSのプロジェクトでは以下のように書きました。

```typescript:main.ts
import { NestFactory } from '@nestjs/core'
import { AppModule } from './app.module'
import * as cookieParser from 'cookie-parser'

async function bootstrap() {
  const app = await NestFactory.create(AppModule)
  app.use(cookieParser())
  await app.listen(3000)
}
bootstrap()
```
### 3. リクエストでcookieを取得する

```typescript:app.controller.ts
import { Controller, Get, Req } from '@nestjs/common'
import { AppService } from './app.service'
import { Request } from 'express'

@Controller()
export class AppController {
  constructor(private readonly appService: AppService) {}

  @Get('/')
  getHello(
    @Req() request: Request,
  ): string {
    console.log('cookie: ', request.cookies)
    return this.appService.getHello()
  }
}
```

```bash:出力
cookie:  [Object: null prototype] {}
```

もちろんなにも設定していないのでcookieの中身は何もありません


### 4. Cookieに任意の値を設定する

```typescript:app.controller.ts
import { Controller, Get, Req, Res } from '@nestjs/common'
import { AppService } from './app.service'
import { Response } from 'express'

@Controller()
export class AppController {
  constructor(private readonly appService: AppService) {}

  @Get('/')
  getHello(@Res({ passthrough: true }) response: Response): string {
    response.cookie('key', 'value', {
      signed: false,
      expires: new Date('2022-12-31'),
      httpOnly: true,
      path: '/',
      domain: 'localhost',
      secure: true,
      sameSite: 'lax',
    })
    return this.appService.getHello()
  }
}

```

```bash:出力
cookie:  { key: 'value' }
```

![スクリーンショット 2022-01-22 14.26.13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/316760/0fc3acb6-1656-373d-fed5-be79c8394ea2.png)

今回はkeyに`key`、valueに`value`を設定しました。
出力結果から分かるように設定されていることがわかります。


`response.cookie('key', 'value', options)`の部分で実際にCookieを設定しています。
singed, expires...等々ありますがそこはoptionsの設定になります。

optionsは以下の記事を参考にしながら設定しました。

https://developer.mozilla.org/ja/docs/Web/HTTP/Headers/Set-Cookie

optionsの各属性の説明は以下になります。

**`maxAge`**
型: **number**

- Cookieの有効期限を秒数で表している
    - Set-Cookieの仕様では秒数だが、実際に指定する際はミリ秒なので注意が必要
- 負の数値だった場合は即時期限切れになる
- 下でも登場するexpiresと両方設定されている場合はmaxAgeが優先される

**`signed`**
型: **boolean**

- 署名付きCookieを利用するかどうかを設定可能
- 利用する場合はcookieParser()にsecretを設定する必要がある

**`expires`**
型： **Date**

- Cookieの有効期限を日時タイムスタンプで表している。
- 指定されなかった場合はセッションと同じ有効期限になる

**`httpOnly`**
型: **boolean**

- HTTPテキスト内のスクリプトでCookieにアクセスできなくするもの
- これによってサイトがXSSの脆弱性を持っていても、Cookieの流出を防ぐことができる

**`path`**
型: **string**

- Cookieを送信するパスの条件
- 上で記載した例だと`/`をpathに含んだページ(同じドメイン内でほぼ全てのページ)に対してCookieが送信される
- `/docs`と設定すると`hogehoge.com/docs`以下のページにのみCookieが送信される

**`domain`**
型: **string**

- Cookieの送信先を設定できる
- 指定されなかった場合は自動的に現在のURLのホスト名が入る

**`secure`**
型: **boolean**

- HTTPS通信でのみCookieを送信する
- 安全でないサイト(http:)との通信を防ぐ役割をしている

**`encode`**
型: **((val: string) => string)**

- valueで設定した値をどのようにエンコードするかを設定できる


**`sameSite`**
型: **boolean | "lax" | "strict" | "none"**

- 他のドメインへのリクエストを送る際にCokieの情報を付与するかどうかを設定できる
    - `strict`では付与しない
    - `lax`ではGETメソッドであれば付与される
    - `none`では全てのリクエストに付与する
- これによりCSRFのような脆弱性を生むのを防ぐことができる

## 余談
全然関係ないのですが昨日参加したQiitaアドベントカレンダーのイベントで[ちょまど](https://twitter.com/chomado)さんの言っていた「記事を書くことが恩返しになると思っている」と言うのを聞いてすごい良い考えだなと思いました。

https://twitter.com/chomado/status/1484501881309200385?s=20

自分じゃどうしようもない課題に直面した際に、調べて出てきた記事は全て救世主みたいですもんね。

そんな風に困っている誰かのためになれれば良いなと言うモチベーションで来週も書いていこうと思います。

ではまた！

## 参考記事

https://docs.nestjs.com/techniques/cookies

https://developer.mozilla.org/ja/docs/Web/HTTP/Cookies

https://developer.mozilla.org/ja/docs/Web/HTTP/Headers/Set-Cookie

https://qiita.com/Kohei-Sato-1221/items/80ff7a0bba6ac9128f56