Websocket Security
title: "WebSocket Security" description: "Learn WebSocket security best practices including origin validation, authentication, WSS encryption, rate limiting, and protection against common attacks." weight: 25 date: 2026-06-28 lastmod: 2026-06-28 tags: ["apis", "websocket"]
WebSocket security requires attention to authentication, encryption, origin validation, and protection against attacks like cross-site WebSocket hijacking and denial of service.
## What You'll Learn
- Cross-site WebSocket hijacking prevention
- Origin header validation
- WebSocket authentication patterns
- WSS encryption requirements
- Rate limiting and DoS protection
- Message validation and sanitization
## Why It Matters
WebSocket connections bypass traditional HTTP security mechanisms. Without proper security, attackers can hijack connections, inject malicious messages, or exhaust server resources.
## Real-World Use
A financial messaging platform implements multi-layered WebSocket security: WSS encryption, JWT authentication during handshake, origin validation, per-user rate limiting, and message schema validation. Security audits confirm no vulnerabilities.
## Flow Chart
```mermaid
flowchart TD
A[Client Connection] --> B{Origin Valid?}
B -->|No| C[Reject]
B -->|Yes| D{Authenticated?}
D -->|No| E[Close 4001]
D -->|Yes| F{Rate Limit?}
F -->|Exceeded| G[Close 4002]
F -->|OK| H[Connected]
H --> I{Message Valid?}
I -->|Invalid| J[Error Response]
I -->|Valid| K[Process]
Code Examples
Example 1: Origin Validation and Authentication
const WebSocket = require('ws');
const jwt = require('jsonwebtoken');
const ALLOWED_ORIGINS = [
'https://app.example.com',
'https://admin.example.com',
];
const server = new WebSocket.Server({
port: 8080,
verifyClient: (info, callback) => {
// Origin validation
const origin = info.origin || info.req.headers.origin;
if (origin && !ALLOWED_ORIGINS.includes(origin)) {
console.warn(`Rejected connection from origin: ${origin}`);
callback(false, 403, 'Forbidden origin');
return;
}
// Token validation from query string or headers
const token = info.req.url.split('token=')[1]
|| info.req.headers.authorization?.replace('Bearer ', '');
if (!token) {
callback(false, 401, 'Authentication required');
return;
}
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
info.req.user = decoded;
callback(true);
} catch (error) {
callback(false, 401, 'Invalid token');
}
},
});
server.on('connection', (ws, req) => {
const user = req.user;
console.log(`Authenticated user ${user.id} connected`);
// Set connection timeout for idle connections
ws._idleTimeout = setTimeout(() => {
ws.close(4000, 'Connection timeout');
}, 300000);
ws.on('message', () => {
// Reset idle timeout on activity
clearTimeout(ws._idleTimeout);
ws._idleTimeout = setTimeout(() => {
ws.close(4000, 'Connection timeout');
}, 300000);
});
});
Expected output: Server validates origin and JWT token before allowing WebSocket connection, rejecting unauthorized origins and unauthenticated requests.
Example 2: Rate Limiting and DoS Protection
const WebSocket = require('ws');
class RateLimiter {
constructor(options = {}) {
this.maxMessages = options.maxMessages || 100;
this.windowMs = options.windowMs || 60000;
this.connections = new Map();
}
check(ws) {
const now = Date.now();
const clientData = this.connections.get(ws) || {
messages: [],
lastWarning: 0,
};
// Clean old messages outside window
clientData.messages = clientData.messages.filter(
time => now - time < this.windowMs
);
if (clientData.messages.length >= this.maxMessages) {
return { allowed: false, retryAfter: this.windowMs };
}
clientData.messages.push(now);
this.connections.set(ws, clientData);
return { allowed: true };
}
remove(ws) {
this.connections.delete(ws);
}
}
const rateLimiter = new RateLimiter({ maxMessages: 60, windowMs: 60000 });
const messageSizeLimiter = new RateLimiter({ maxMessages: 1048576 }); // 1MB per minute
const server = new WebSocket.Server({ port: 8080 });
server.on('connection', (ws) => {
ws.on('message', (data) => {
// Rate limit check
const rateCheck = rateLimiter.check(ws);
if (!rateCheck.allowed) {
ws.close(4002, 'Rate limit exceeded');
return;
}
// Message size validation
if (data.length > 65536) { // 64KB max
ws.close(1009, 'Message too large');
return;
}
// Message depth validation (prevent deep nesting attacks)
try {
const parsed = JSON.parse(data);
if (getDepth(parsed) > 10) {
ws.close(1003, 'Message nesting too deep');
return;
}
} catch {
ws.close(1007, 'Invalid payload');
}
});
ws.on('close', () => {
rateLimiter.remove(ws);
messageSizeLimiter.remove(ws);
});
});
function getDepth(obj, depth = 0) {
if (depth > 20) return depth;
if (obj && typeof obj === 'object') {
return Math.max(0, ...Object.values(obj).map(
v => getDepth(v, depth + 1)
));
}
return depth;
}
Expected output: Server enforces rate limits, message size limits, and prevents deep nesting attacks.
Example 3: Input Sanitization and Safe Broadcasting
const WebSocket = require('ws');
const createDOMPurify = require('dompurify');
const { JSDOM } = require('jsdom');
const window = new JSDOM('').window;
const DOMPurify = createDOMPurify(window);
const server = new WebSocket.Server({ port: 8080 });
server.on('connection', (ws) => {
ws.on('message', (data) => {
try {
const message = JSON.parse(data);
// Sanitize string fields
if (message.text) {
message.text = DOMPurify.sanitize(message.text, {
ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'a'],
ALLOWED_ATTR: ['href'],
});
// Length limit after sanitization
if (message.text.length > 5000) {
message.text = message.text.substring(0, 5000);
}
}
// Validate user ID matches authenticated user
if (message.userId && message.userId !== ws.authenticatedUser.id) {
ws.send(JSON.stringify({
type: 'error',
code: 'FORBIDDEN',
message: 'Cannot impersonate another user',
}));
return;
}
// Rate limit per channel
const channel = message.channel || 'global';
if (isChannelRateLimited(ws, channel)) {
return;
}
// Broadcast sanitized message
broadcast(message, ws);
} catch (error) {
ws.send(JSON.stringify({
type: 'error',
code: 'INVALID_MESSAGE',
message: 'Invalid message format',
}));
}
});
});
function broadcast(message, sender) {
server.clients.forEach((client) => {
if (client.readyState === WebSocket.OPEN) {
// Do not send to sender
if (client === sender && message.noEcho) return;
// Check if client has permission for this channel
if (!client.subscribedChannels?.has(message.channel)) return;
client.send(JSON.stringify({
...message,
sanitized: true,
timestamp: Date.now(),
}));
}
});
}
Expected output: Messages are sanitized to prevent XSS, user identity is verified, and broadcasting respects channel subscriptions.
Common Mistakes
| Mistake | Explanation |
|---|---|
| Not validating Origin header | Without origin validation, any website can connect to your WebSocket server |
| Using ws:// in production | Unencrypted connections expose all data to eavesdropping; always use wss:// |
| Not implementing rate limiting | Without rate limits, attackers can exhaust server resources with high-frequency messages |
| Trusting client-provided user IDs | Always use server-authenticated user identity, never trust client-provided IDs |
| Ignoring message size limits | Large messages can exhaust memory; enforce reasonable size limits |
Practice Questions
- What is cross-site WebSocket hijacking and how do you prevent it?
- How do you authenticate WebSocket connections?
- Why is origin validation important for WebSocket security?
- What rate limiting strategies work for WebSocket connections?
- How do you prevent message injection attacks in WebSocket?
Challenge
Audit a WebSocket application for security vulnerabilities. Implement origin validation, JWT authentication, rate limiting, message sanitization, and WSS encryption. Write security tests that verify each protection mechanism works.
FAQ
Mini Project
Build a secure WebSocket server with comprehensive security features: origin validation, JWT authentication with auto-refresh, per-user rate limiting, message sanitization, WSS with valid certificates, and connection monitoring with automatic blocking of malicious IPs.
What's Next
Build a WebSocket chat application
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro