Nest.js × WebSocketのGuardの中でcookieを使う

2024-02-10

アイキャッチ

Nest.jsでWebSocketの実装時、認証のGuardを貼りたいときなどGuardの中でcookieを取得したい時があります。

要はExecutionContextのどこからcookieを取り出すかですが他にも必要な設定が足りていないとcookie取得できないのでやり方をまとめていきます。

環境

今回動作確認する環境は以下。

バックエンド

Nest.js

http://localhost:8000

クライアント

Next.js(App Router)

http://localhost:3000

バックエンドの作成

Nest.jsのバックエンドを設定します。

main.tsでクライアントからのリクエストのcorsの許可設定、cookieのやり取りをするためにcredentials: trueを設定します。

corsの設定はGatewayで行うためmain.tsはデフォルト。

src/main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  await app.listen(8000);
}
bootstrap();

適当なGatewayを作成します。

src/websocket/websocket.gateway.ts
import {
  WebSocketGateway,
  SubscribeMessage,
  MessageBody,
  WebSocketServer,
} from '@nestjs/websockets';
import { CreateWebsocketDto } from './dto/create-websocket.dto';
import { Server } from 'socket.io';
import { Logger } from '@nestjs/common';

@WebSocketGateway()
export class WebsocketGateway {
  private readonly logger: Logger = new Logger('Gateway Log');
  @WebSocketServer()
  private readonly server: Server;

  @SubscribeMessage('websocket')
  websocket(@MessageBody() data: CreateWebsocketDto) {
    this.logger.log(data);
    this.server.emit('websocket', data);
  }
}

受け取ったデータをログに出してそのままクライアントに渡してみました。

ここに適応するGuardを作成します。下記コマンドでファイル作成。

$ nest g gu auth

GuardcanActivateメソッドの引数はcontext: ExecutionContextのパラメータを受け取ることができ、現在の実行コンテキストに関する情報を提供します。
https://docs.nestjs.com/fundamentals/execution-context

websocketでcontextからcookieを受け取るには次のようにします。

src/auth/auth.guard.ts
import {
  CanActivate,
  ExecutionContext,
  Injectable,
  Logger,
} from '@nestjs/common';
import { Observable } from 'rxjs';

@Injectable()
export class AuthGuard implements CanActivate {
  private readonly logger: Logger = new Logger('AuthGuard Log');
  canActivate(
    context: ExecutionContext,
  ): boolean | Promise<boolean> | Observable<boolean> {
    const client = context.switchToWs().getClient();
    const cookies: string[] = client.handshake.headers.cookie.split('; ');
    this.logger.log(cookies);
    return true;
  }
}

cookieが取得できているか確認したいだけなのでtrueを返しておきます。headersまでの取得はテンプレで覚えてしまってもいいような気がします。

このGuardを先ほどのGatewayに設定します。ついでにcorsの許可やcookieのやり取りに必要な設定も追加します。

src/websocket/websocket.gateway.ts
import {
  WebSocketGateway,
  SubscribeMessage,
  MessageBody,
  WebSocketServer,
} from '@nestjs/websockets';
import { CreateWebsocketDto } from './dto/create-websocket.dto';
import { Server } from 'socket.io';
import { Logger, UseGuards } from '@nestjs/common';
import { AuthGuard } from 'src/auth/auth.guard';

@WebSocketGateway({
  cors: {
    // corsの許可
    origin: 'http://localhost:3000',
    // cookieのやり取りに必要な設定
    credentials: true,
  },
})
// 先ほど作成したGuardを設定
@UseGuards(new AuthGuard())
export class WebsocketGateway {
  private readonly logger: Logger = new Logger('Gateway Log');
  @WebSocketServer()
  private readonly server: Server;

  @SubscribeMessage('websocket')
  websocket(@MessageBody() data: CreateWebsocketDto) {
    this.logger.log(data);
    this.server.emit('websocket', data);
  }
}

バックエンドはこれで設定完了。
続いてReactでクライアントからデータを送信してみます。

クライアントの作成

socket.io-clientで受信してログに出力、ボタンでの送信を行います。
cookieの送信のためにwithCredentials: trueの設定を行います。

src/app/page.tsx
"use client";
import { useEffect } from "react";
import { io } from "socket.io-client";

export default function Home() {
	const socket = io("http://localhost:8000", { withCredentials: true });

	useEffect(() => {
		socket.on("websocket", (data) => {
			console.log(data);
		});
	}, []);

	function handleClick() {
		socket.emit("websocket", { message: "hello" });
	}
	return <button onClick={handleClick}>send</button>;
}

動作確認

http://localhost:3000/へアクセスし動作確認してみます。
ボタンを押すと、バックエンドのターミナルにcookieと送信したデータが表示されました。

[Nest] 13151  - 2024/02/10 19:52:45     LOG [AuthGuard Log] Object:
[
  "_ga=xxxxxxxxxxxxxxxxxxxxxx"
]

[Nest] 13151  - 2024/02/10 19:52:45     LOG [Gateway Log] Object:
{
  "message": "hello"
}

お仕事のお知らせはこちらからお願いいたします。

お問い合わせ