Skip to content

Websocket Security

DodaTech 6 min read

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

  1. What is cross-site WebSocket hijacking and how do you prevent it?
  2. How do you authenticate WebSocket connections?
  3. Why is origin validation important for WebSocket security?
  4. What rate limiting strategies work for WebSocket connections?
  5. 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

Is WebSocket vulnerable to CSRF attacks?

WebSocket is not vulnerable to traditional CSRF because it does not use cookies for authentication. However, it is vulnerable to cross-site WebSocket hijacking without origin validation.

Should I validate Origin header in all environments?

Validate Origin in production. In development, allow common origins like localhost and 127.0.0.1 for convenience.

How do I handle token expiration on WebSocket?

Use short-lived tokens (15-60 min) and implement a token refresh mechanism that sends new tokens to the client before expiry.

Can I use cookies for WebSocket authentication?

Yes, but be careful with same-origin policies. Cookies set by HTTPS are not sent to WS (only WSS). Use tokens for cross-origin scenarios.

What is the best way to store WebSocket tokens on the client?

Store tokens in memory (not localStorage) to prevent XSS-based token theft. Use httpOnly cookies for server-set tokens.

How do I protect against WebSocket DoS attacks?

Implement per-connection rate limiting, message size limits, connection timeouts, and connection rate limits (new connections per second per IP).

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