Skip to content

WebSocket Security — Complete Guide

DodaTech Updated 2026-06-28 3 min read

In this tutorial, you will learn about WebSocket Securityet" >}} Security. We cover key concepts, practical examples, and best practices to help you master this topic.

WebSocket security requires protection at multiple levels: transport (TLS), handshake (origin validation), authentication (token verification), and message level (validation, Rate Limiting, size limits). Unlike HTTP, there are no built-in CSRF protections.

What You'll Learn

  • Cross-Site WebSocket Hijacking (CSWSH) prevention
  • Origin header validation
  • Input sanitization and validation
  • Rate limiting and message size limits
  • TLS and authentication enforcement

Why It Matters

WebSocket bypasses standard web security mechanisms like CSRF tokens. Without origin validation, any website can open a WebSocket connection to your API using the user's existing session.

Real-World Use

Slack validates Origin headers. Discord uses token authentication in the WebSocket handshake. Trading platforms enforce strict input validation and rate limiting on WebSocket endpoints.

flowchart TD
    Request[WebSocket Request] --> TLS{TLS Enabled?}
    TLS -->|No| Reject[Reject Connection]
    TLS -->|Yes| Origin{Origin Valid?}
    Origin -->|Invalid| Reject
    Origin -->|Valid| Auth{Authenticated?}
    Auth -->|No| Reject
    Auth -->|Yes| Rate{Rate Limit OK?}
    Rate -->|Exceeded| Reject
    Rate -->|OK| Accept[Accept Connection]
    Accept --> Validate[Validate Messages]

Teacher Mindset

Treat every WebSocket connection as potentially malicious. Validate the origin. Require authentication. Set rate limits and message size limits. Sanitize all incoming data. Never trust the client.

Code Examples

// Example 1: Origin validation
const WebSocket = require('ws');
const allowedOrigins = ['https://app.example.com', 'https://admin.example.com'];

const server = new WebSocket.Server({ port: 8080 });

server.on('connection', (ws, req) => {
  const origin = req.headers.origin;

  if (!origin || !allowedOrigins.includes(origin)) {
    console.warn(`Blocked connection from origin: ${origin}`);
    ws.close(1008, 'Origin not allowed');
    return;
  }

  console.log(`Allowed connection from: ${origin}`);
});
// Example 2: Input validation and sanitization
const server = new WebSocket.Server({ port: 8080 });

server.on('connection', (ws) => {
  ws.on('message', (data) => {
    try {
      const message = JSON.parse(data);

      // Validate structure
      if (!message.type || !message.payload) {
        ws.send(JSON.stringify({ error: 'Invalid message format' }));
        return;
      }

      // Validate type
      const allowedTypes = ['chat', 'join', 'leave'];
      if (!allowedTypes.includes(message.type)) {
        ws.send(JSON.stringify({ error: 'Unknown message type' }));
        return;
      }

      // Sanitize text content
      if (message.payload.text) {
        message.payload.text = sanitizeHtml(message.payload.text);
      }

      // Check message size
      const size = Buffer.byteLength(JSON.stringify(message), 'utf8');
      if (size > 10000) {
        ws.send(JSON.stringify({ error: 'Message too large' }));
        return;
      }

      // Process valid message
      handleMessage(ws, message);

    } catch (err) {
      ws.send(JSON.stringify({ error: 'Invalid JSON' }));
    }
  });
});
// Example 3: WebSocket rate limiting
const rateLimits = new Map();

function wsRateLimit(ws, req) {
  const clientIp = req.socket.remoteAddress;
  const now = Date.now();
  const windowMs = 10000;
  const maxRequests = 20;

  if (!rateLimits.has(clientIp)) {
    rateLimits.set(clientIp, []);
  }

  const timestamps = rateLimits.get(clientIp);
  const recent = timestamps.filter(t => now - t < windowMs);
  recent.push(now);
  rateLimits.set(clientIp, recent);

  if (recent.length > maxRequests) {
    ws.close(1008, 'Rate limit exceeded');
    return false;
  }

  ws.on('close', () => {
    rateLimits.delete(clientIp);
  });

  return true;
}

server.on('connection', (ws, req) => {
  if (!wsRateLimit(ws, req)) return;
  // Normal connection handling
});

Common Mistakes

  • Not validating the Origin header, leaving the API vulnerable to CSWSH
  • Sending sensitive data (tokens, user data) over unencrypted ws://
  • Not sanitizing HTML in chat messages (XSS via WebSocket)
  • Allowing unlimited message sizes (DoS risk)
  • Trusting client-provided user IDs without server-side validation

Practice

  1. Add origin validation to your WebSocket server.
  2. Implement JSON message validation with allowed types.
  3. Add server-side rate limiting per IP address.
  4. Set message size limits and reject oversized messages.
  5. Challenge: Implement a comprehensive security middleware that checks origin, rate limit, message size, and message schema.

FAQ

What is Cross-Site WebSocket Hijacking?

CSWSH occurs when a malicious website opens a WebSocket connection to your API using the victim's credentials. Origin validation prevents this.

Does WebSocket need CSRF protection?

Origin validation replaces CSRF for WebSocket. Traditional CSRF tokens do not work because WebSocket has no custom headers.

How do I prevent XSS in WebSocket messages?

Sanitize HTML in messages before storing or forwarding. Use DOMPurify or similar libraries.

What is the recommended message size limit?

10-100 KB per message is typical for chat applications. Adjust based on your use case.

Should I rate limit WebSocket connections or messages?

Both. Limit connection attempts per IP and messages per connection per time window.

Mini Project

Add four security layers to your WebSocket server: TLS enforcement (wss:// only), origin validation against an allowed list, token-based authentication during handshake, and per-connection rate limiting (30 messages per 30 seconds).

What's Next

Next, you will learn about WebSocket deployment strategies for production environments.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro