WebSocket

WebSockets don't have a traditional request/response cycle. You verify the token when the client connects, then store the user data on the socket.

Verify on connection

import { WebSocketGateway, OnGatewayConnection } from '@nestjs/websockets';
import { Socket } from 'socket.io';
import { CognitoJwtVerifier, InjectCognitoJwtVerifier } from '@nestjs-cognito/core';

@WebSocketGateway()
export class MessagesGateway implements OnGatewayConnection {
  constructor(
    @InjectCognitoJwtVerifier()
    private readonly jwtVerifier: CognitoJwtVerifier
  ) {}

  async handleConnection(client: Socket) {
    try {
      const token = client.handshake.headers.authorization?.replace('Bearer ', '');
      if (!token) {
        client.disconnect();
        return;
      }

      const payload = await this.jwtVerifier.verify(token);
      client.data.user = payload;
    } catch (error) {
      client.disconnect();
    }
  }
}

Client:

import { io } from 'socket.io-client';

const socket = io('http://localhost:3000', {
  extraHeaders: { authorization: `Bearer ${token}` }
});

From cookies

import { parse } from 'cookie';

@WebSocketGateway({ cors: { origin: 'http://localhost:3000', credentials: true } })
export class MessagesGateway implements OnGatewayConnection {
  constructor(
    @InjectCognitoJwtVerifier()
    private readonly jwtVerifier: CognitoJwtVerifier
  ) {}

  async handleConnection(client: Socket) {
    try {
      const cookies = parse(client.handshake.headers.cookie || '');
      const token = cookies['access_token'];

      if (!token) {
        client.disconnect();
        return;
      }

      const payload = await this.jwtVerifier.verify(token);
      client.data.user = payload;
    } catch (error) {
      client.disconnect();
    }
  }
}

Client:

const socket = io('http://localhost:3000', {
  withCredentials: true
});

Check groups in handlers

import { SubscribeMessage, MessageBody, ConnectedSocket } from '@nestjs/websockets';
import type { CognitoJwtPayload } from '@nestjs-cognito/core';

@WebSocketGateway()
export class MessagesGateway {
  @SubscribeMessage('adminAction')
  handleAdminAction(
    @MessageBody() data: any,
    @ConnectedSocket() client: Socket,
  ) {
    const user: CognitoJwtPayload = client.data.user;
    const groups = user['cognito:groups'] || [];

    if (!groups.includes('admin')) {
      throw new UnauthorizedException('Admin access required');
    }

    return { success: true };
  }
}