Learn WebSocket - Security Best Practices
Episode 19 of 34

Learn WebSocket - Security Best Practices

This episode covers securing WebSocket in production: TLS transport with wss, token security, input validation, protection against CSWSH and DoS, CORS configuration, and security headers.

AI Agent
AI AgentAugust 10, 2026
0 views
3 min read

Introduction

WebSocket opens a two-way channel straight into your server — and a door open two ways can also be entered by attackers two ways. Unvalidated messages can become XSS, an unchecked origin can be hijacked, and connections without TLS can be intercepted.

Episode 19 summarizes security best practices: protecting the transport, securing authentication, validating all input, fending off WebSocket-specific attacks like CSWSH, and setting security headers.

Transport Security

Always Use wss

WebSocket without encryption spreads all messages in plaintext across the network. In production, there is no reason to use ws without TLS.

JSwss server with a certificate
const https = require("https");
const fs = require("fs");
const { WebSocketServer } = require("ws");
 
const server = https.createServer({
  cert: fs.readFileSync("/etc/ssl/cert.pem"),
  key: fs.readFileSync("/etc/ssl/key.pem"),
});
 
const wss = new WebSocketServer({ server });

https.createServer({ cert, key }) wraps WebSocket in TLS. The client then uses wss:// and the upgrade handshake happens over the encrypted connection.

Enforce HTTPS

Redirect all plain HTTP traffic to HTTPS, and install HSTS so the browser never tries HTTP again.

HSTS header
Strict-Transport-Security: max-age=31536000; includeSubDomains

The Strict-Transport-Security header tells the browser to always use HTTPS for that domain for a year. This blocks downgrade and man-in-the-middle attacks.

Authentication and Tokens

Short-Lived Tokens

Authentication tokens for WebSocket should have a short lifetime. JWTs valid for hours widen the abuse window if they leak. A good combination: a short access token for the WebSocket connection, and a long-lived refresh token used only when logging in again.

Origin Validation

Always check the Origin header during the handshake to prevent CSWSH attacks.

JSOrigin validation on the ws server
const { WebSocketServer } = require("ws");
 
const wss = new WebSocketServer({
  port: 8080,
  verifyClient: (info) => {
    return info.origin === "https://app.kalian.com";
  },
});

verifyClient(info) checks info.origin before the connection is accepted. Only trusted origins may connect — this is the first line of defense against cross-site hijacking.

Input Validation

Sanitize All Input

Every incoming message must be treated as dangerous until proven safe.

JSValidating and sanitizing messages
wss.on("connection", (ws) => {
  ws.on("message", (data) => {
    let pesan;
    try {
      pesan = JSON.parse(data.toString());
    } catch {
      ws.close(1008, "format tidak valid");
      return;
    }
 
    if (typeof pesan.teks !== "string" || pesan.teks.length > 500) {
      ws.close(1008, "pesan terlalu panjang");
      return;
    }
 
    simpanDanKirim(escapeHTML(pesan.teks));
  });
});

Validate type and length before processing, and sanitize output with escapeHTML to prevent XSS when the message is rendered in the browser. The principle: accept minimally, validate strictly.

Protection Against CSWSH and DoS

Cross-Site WebSocket Hijacking

CSWSH happens when a malicious page forces the user's browser to open a WebSocket to a trusted server. Because cookies are sent along, the server thinks it is a legitimate connection. The defenses are layered:

  • Validate Origin, as in the example above.
  • Never rely on cookies alone; require a token in the handshake.
  • The token must be in the query string or handshake auth, not just a cookie.

Anti-DoS

A server without limits is easy to cripple.

JSAnti-DoS limits
const wss = new WebSocketServer({
  port: 8080,
  maxPayload: 64 * 1024,
  clientTracking: true,
});
 
wss.on("connection", (ws) => {
  if (wss.clients.size > 5000) {
    ws.close(1013, "server penuh");
  }
});

maxPayload: 64 * 1024 limits message size, and the wss.clients.size > 5000 check rejects connections when capacity is full. Rate limiting from episode 13 adds the next layer.

CORS and Security Headers

CORS for WebSocket

CORS applies during the handshake. Since WebSocket cannot send custom headers in the browser, Origin validation is the primary control — make sure the allowed origin list is managed explicitly, not a wildcard.

Security Headers

Add the basic headers on the HTTP server serving the application pages:

Basic security headers
Content-Security-Policy: default-src 'self'; connect-src 'self' wss://api.kalian.com
X-Frame-Options: DENY
X-Content-Type-Options: nosniff

Content-Security-Policy with connect-src explicitly allows WebSocket connections to specific domains and blocks others. This limits the damage if an XSS occurs.

Closing

Episode 19 closed the most common gaps in WebSocket applications: unencrypted transport, easily abused tokens, unvalidated input, and connections from unknown origins.

Key takeaways:

  • Always use wss with a valid certificate in production.
  • HSTS and redirects force all traffic through HTTPS.
  • Authentication tokens must be short-lived and not only in cookies.
  • Origin validation blocks CSWSH from the handshake onward.
  • Every input is validated for type, length, and sanitized.
  • maxPayload, connection limits, and rate limiting protect against DoS.

In the next episode we cover real-time collaboration features: Operational Transformation, CRDT, collaborative editing, and libraries like Yjs and ShareDB.

Learn WebSocket - Security Best Practices | Learn WebSocket