Step-by-Step Guide
6 steps

How to Secure WebSocket Connections

WebSockets enable real-time bidirectional communication but introduce unique security challenges. Without authentication, origin checking, and input validation, WebSocket connections can be exploited for data theft, injection attacks, and denial of service.

Find security issues automatically before attackers do.

Follow These Steps

1

Always use WSS (WebSocket Secure)

Use wss:// instead of ws:// to encrypt WebSocket traffic.

Code Example
// Client
const ws = new WebSocket('wss://yourdomain.com/ws')

// Server (using the web server TLS certificate)
import { WebSocketServer } from 'ws'
import https from 'https'

const server = https.createServer({ cert, key })
const wss = new WebSocketServer({ server })
2

Authenticate WebSocket connections

Verify the user before accepting the WebSocket connection.

Code Example
import { WebSocketServer } from 'ws'
import { verifyToken } from './auth'

const wss = new WebSocketServer({ noServer: true })

server.on('upgrade', async (request, socket, head) => {
  try {
    const token = new URL(request.url!, `http://${request.headers.host}`)
      .searchParams.get('token')
    
    if (!token) {
      socket.destroy()
      return
    }
    
    const user = await verifyToken(token)
    
    wss.handleUpgrade(request, socket, head, (ws) => {
      ws.user = user
      wss.emit('connection', ws, request)
    })
  } catch {
    socket.destroy()
  }
})
3

Validate the Origin header

Check that WebSocket connections come from your domain.

Code Example
wss.on('connection', (ws, req) => {
  const origin = req.headers.origin
  const allowedOrigins = ['https://yourdomain.com']
  
  if (!origin || !allowedOrigins.includes(origin)) {
    ws.close(1008, 'Invalid origin')
    return
  }
})
4

Validate all incoming messages

Treat WebSocket messages like any other user input. Validate type and content.

Code Example
import { z } from 'zod'

const MessageSchema = z.object({
  type: z.enum(['chat', 'typing', 'ping']),
  content: z.string().max(5000).optional(),
  channelId: z.string().uuid().optional()
})

ws.on('message', (data) => {
  try {
    const raw = JSON.parse(data.toString())
    const message = MessageSchema.parse(raw)
    handleMessage(ws, message)
  } catch {
    ws.send(JSON.stringify({ error: 'Invalid message format' }))
  }
})
5

Implement rate limiting on messages

Prevent message flooding from individual connections.

Code Example
const messageRates = new Map<string, number[]>()
const MAX_MESSAGES_PER_SECOND = 10

function checkMessageRate(userId: string): boolean {
  const now = Date.now()
  const timestamps = messageRates.get(userId) || []
  const recent = timestamps.filter(t => now - t < 1000)
  
  if (recent.length >= MAX_MESSAGES_PER_SECOND) {
    return false // Rate limited
  }
  
  recent.push(now)
  messageRates.set(userId, recent)
  return true
}

ws.on('message', (data) => {
  if (!checkMessageRate(ws.user.id)) {
    ws.send(JSON.stringify({ error: 'Rate limit exceeded' }))
    return
  }
  // Process message
})
6

Handle disconnections and cleanup

Clean up resources when connections close to prevent memory leaks.

Code Example
ws.on('close', () => {
  messageRates.delete(ws.user.id)
  removeFromChannels(ws.user.id)
})

ws.on('error', (error) => {
  console.error('WebSocket error:', error)
  ws.terminate()
})

// Set a ping/pong heartbeat to detect dead connections
const interval = setInterval(() => {
  wss.clients.forEach((ws) => {
    if (!ws.isAlive) return ws.terminate()
    ws.isAlive = false
    ws.ping()
  })
}, 30000)

ws.on('pong', () => { ws.isAlive = true })

What You'll Achieve

WebSocket connections use WSS encryption, require authentication, validate origin, validate all messages, implement rate limiting, and properly clean up on disconnection.

Common Mistakes to Avoid

Mistake

Using ws:// instead of wss://

Fix

Always use wss:// (WebSocket Secure) in production. ws:// transmits data in plaintext, allowing eavesdropping and tampering.

Mistake

Not authenticating WebSocket connections

Fix

Authenticate during the upgrade handshake. Pass a token as a query parameter or in cookies, and verify before accepting the connection.

Mistake

Not validating WebSocket messages

Fix

WebSocket messages are user input and must be validated. Use schema validation for every message type.

Frequently Asked Questions

Are WebSockets affected by CORS?

WebSocket connections are not subject to CORS. The browser sends the Origin header but does not enforce it. You must validate the Origin header on the server side.

How do I authenticate WebSocket connections?

Pass a JWT or session token during the connection upgrade. Verify it on the server before accepting the connection. You cannot set custom headers on WebSocket connections from browsers, so use query parameters or cookies.

Ready to Secure Your App?

VAS automatically scans your deployed app for the security issues covered in this guide. Get actionable results in minutes.

Start Security Scan