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
Always use WSS (WebSocket Secure)
Use wss:// instead of ws:// to encrypt WebSocket traffic.
// 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 })Authenticate WebSocket connections
Verify the user before accepting the WebSocket connection.
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()
}
})Validate the Origin header
Check that WebSocket connections come from your domain.
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
}
})Validate all incoming messages
Treat WebSocket messages like any other user input. Validate type and content.
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' }))
}
})Implement rate limiting on messages
Prevent message flooding from individual connections.
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
})Handle disconnections and cleanup
Clean up resources when connections close to prevent memory leaks.
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