Strengthen WebSocket authentication #198

Closed
opened 2026-02-02 17:27:10 +00:00 by jason.woltje · 0 comments
Owner

Problem

WebSocket authentication only checks for presence of userId/workspaceId in client.data but doesn't validate if these values are actually authenticated or if user has workspace access.

Location

apps/api/src/websocket/websocket.gateway.ts:94-100

const { userId, workspaceId } = authenticatedClient.data;

if (!userId || !workspaceId) {
  this.logger.warn(`Client ${authenticatedClient.id} without auth`);
  authenticatedClient.disconnect();
  return;
}
// ❌ Where is client.data populated?
// ❌ Is it validated?
// ❌ Does user have workspace access?

Issues

  1. No validation of user authentication
  2. No workspace access check
  3. client.data could be set by anyone
  4. No JWT verification
  5. No audit log of connections

Acceptance Criteria

  • Implement WsAuthGuard with JWT validation
  • Verify user has workspace access
  • Log all WebSocket connections
  • Add connection rate limiting
  • Add tests for auth failures
  • Document WebSocket auth flow

Implementation

@UseGuards(WsAuthGuard)
@WebSocketGateway(/* ... */)
export class WebSocketGateway {
  async handleConnection(
    @ConnectedSocket() client: Socket
  ) {
    // Guard validates JWT and populates user data
    const { userId, workspaceId } = client.data;
    
    // Verify workspace access
    const hasAccess = await this.workspaceService.hasAccess(
      userId, 
      workspaceId
    );
    
    if (!hasAccess) {
      this.logger.warn(`User ${userId} no access to workspace ${workspaceId}`);
      client.disconnect();
      return;
    }
    
    this.logger.log(`User ${userId} connected to workspace ${workspaceId}`);
  }
}

// Guard implementation
@Injectable()
export class WsAuthGuard implements CanActivate {
  canActivate(context: ExecutionContext): boolean {
    const client = context.switchToWs().getClient<Socket>();
    const token = this.extractToken(client);
    
    if (!token) return false;
    
    const payload = this.jwtService.verify(token);
    client.data = payload;
    return true;
  }
}

Testing

  • Test connection without token rejected
  • Test expired token rejected
  • Test user without workspace access rejected
  • Test valid connection succeeds

References

M4.2-Infrastructure verification report (2026-02-02)

## Problem WebSocket authentication only checks for presence of userId/workspaceId in client.data but doesn't validate if these values are actually authenticated or if user has workspace access. ## Location apps/api/src/websocket/websocket.gateway.ts:94-100 ```typescript const { userId, workspaceId } = authenticatedClient.data; if (!userId || !workspaceId) { this.logger.warn(`Client ${authenticatedClient.id} without auth`); authenticatedClient.disconnect(); return; } // ❌ Where is client.data populated? // ❌ Is it validated? // ❌ Does user have workspace access? ``` ## Issues 1. No validation of user authentication 2. No workspace access check 3. client.data could be set by anyone 4. No JWT verification 5. No audit log of connections ## Acceptance Criteria - [ ] Implement WsAuthGuard with JWT validation - [ ] Verify user has workspace access - [ ] Log all WebSocket connections - [ ] Add connection rate limiting - [ ] Add tests for auth failures - [ ] Document WebSocket auth flow ## Implementation ```typescript @UseGuards(WsAuthGuard) @WebSocketGateway(/* ... */) export class WebSocketGateway { async handleConnection( @ConnectedSocket() client: Socket ) { // Guard validates JWT and populates user data const { userId, workspaceId } = client.data; // Verify workspace access const hasAccess = await this.workspaceService.hasAccess( userId, workspaceId ); if (!hasAccess) { this.logger.warn(`User ${userId} no access to workspace ${workspaceId}`); client.disconnect(); return; } this.logger.log(`User ${userId} connected to workspace ${workspaceId}`); } } // Guard implementation @Injectable() export class WsAuthGuard implements CanActivate { canActivate(context: ExecutionContext): boolean { const client = context.switchToWs().getClient<Socket>(); const token = this.extractToken(client); if (!token) return false; const payload = this.jwtService.verify(token); client.data = payload; return true; } } ``` ## Testing - [ ] Test connection without token rejected - [ ] Test expired token rejected - [ ] Test user without workspace access rejected - [ ] Test valid connection succeeds ## References M4.2-Infrastructure verification report (2026-02-02)
jason.woltje added this to the M4.2-Infrastructure (0.0.4) milestone 2026-02-02 17:27:10 +00:00
jason.woltje added the securityp2apiapi labels 2026-02-02 17:27:10 +00:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: mosaic/stack#198