From 8ffa47423b160066f233908fadceea06a9912455 Mon Sep 17 00:00:00 2001 From: Faiz Khairi Date: Sun, 28 Jun 2026 01:12:05 +0800 Subject: [PATCH 1/2] docs(websockets): add JWT connection authentication guide Document validating JWT credentials in handleConnection for socket.io handshakes, including client auth example and room assignment pattern. --- content/websockets/guards.md | 101 +++++++++++++++++++++++++++++++++++ 1 file changed, 101 insertions(+) diff --git a/content/websockets/guards.md b/content/websockets/guards.md index 51a44c6228..099c35db76 100644 --- a/content/websockets/guards.md +++ b/content/websockets/guards.md @@ -24,3 +24,104 @@ handleEvent(client, data) { return { event, data }; } ``` + +#### Authenticating connections + +Guards bound with `@UseGuards()` protect `@SubscribeMessage()` handlers, but the initial Socket.IO connection is established in `handleConnection()` before those handlers run. To reject unauthorized clients at connect time, validate credentials inside `OnGatewayConnection.handleConnection()` and call `client.disconnect()` when validation fails. + +With socket.io, clients can pass a JWT in `handshake.auth` (recommended) or the `Authorization` header: + +```typescript +@@filename(events.gateway) +import { ConnectedSocket, OnGatewayConnection, WebSocketGateway } from '@nestjs/websockets'; +import { Socket } from 'socket.io'; +import { JwtService } from '@nestjs/jwt'; + +@WebSocketGateway({ namespace: 'events' }) +export class EventsGateway implements OnGatewayConnection { + constructor(private readonly jwtService: JwtService) {} + + async handleConnection(@ConnectedSocket() client: Socket) { + const token = this.extractToken(client); + + if (!token) { + client.disconnect(); + return; + } + + try { + const payload = await this.jwtService.verifyAsync(token); + await client.join(`user:${payload.sub}`); + } catch { + client.disconnect(); + } + } + + private extractToken(client: Socket): string | undefined { + const authToken = client.handshake.auth?.token; + + if (typeof authToken === 'string') { + return authToken.replace(/^Bearer\s+/i, ''); + } + + const authorization = client.handshake.headers.authorization; + + if (typeof authorization === 'string') { + return authorization.replace(/^Bearer\s+/i, ''); + } + } +} +@@switch +import { WebSocketGateway } from '@nestjs/websockets'; + +@WebSocketGateway({ namespace: 'events' }) +export class EventsGateway { + constructor(jwtService) { + this.jwtService = jwtService; + } + + async handleConnection(client) { + const token = this.extractToken(client); + + if (!token) { + client.disconnect(); + return; + } + + try { + const payload = await this.jwtService.verifyAsync(token); + await client.join(`user:${payload.sub}`); + } catch { + client.disconnect(); + } + } + + extractToken(client) { + const authToken = client.handshake.auth?.token; + + if (typeof authToken === 'string') { + return authToken.replace(/^Bearer\s+/i, ''); + } + + const authorization = client.handshake.headers.authorization; + + if (typeof authorization === 'string') { + return authorization.replace(/^Bearer\s+/i, ''); + } + } +} +``` + +Client example: + +```typescript +import { io } from 'socket.io-client'; + +const socket = io('http://localhost:3000/events', { + auth: { + token: 'Bearer ', + }, +}); +``` + +> info **Hint** Method-scoped guards still apply to incoming messages after the connection is accepted. Use both patterns when you need to protect the handshake and individual events. From 0725a0ae9beb4d528972bd97935753363fd4e3d5 Mon Sep 17 00:00:00 2001 From: Faiz Khairi Date: Sun, 28 Jun 2026 08:14:52 +0800 Subject: [PATCH 2/2] fix(websockets): import extension pipe in guards page component Required when guards.md uses named filename code blocks; fixes Netlify deploy preview build failure on PR #3463. --- src/app/homepage/pages/websockets/guards/guards.component.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/app/homepage/pages/websockets/guards/guards.component.ts b/src/app/homepage/pages/websockets/guards/guards.component.ts index f411790d38..26b006245e 100644 --- a/src/app/homepage/pages/websockets/guards/guards.component.ts +++ b/src/app/homepage/pages/websockets/guards/guards.component.ts @@ -4,6 +4,7 @@ import { RouterLink } from '@angular/router'; import { HeaderAnchorDirective } from '../../../../shared/directives/header-anchor.directive'; import { CopyButtonComponent } from '../../../../shared/components/copy-button/copy-button.component'; import { TabsComponent } from '../../../../shared/components/tabs/tabs.component'; +import { ExtensionPipe } from '../../../../shared/pipes/extension.pipe'; @Component({ selector: 'app-guards', @@ -15,6 +16,7 @@ import { TabsComponent } from '../../../../shared/components/tabs/tabs.component HeaderAnchorDirective, CopyButtonComponent, TabsComponent, + ExtensionPipe, ], }) export class WsGuardsComponent extends BasePageComponent {}