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.

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.
WebSocket without encryption spreads all messages in plaintext across the network. In production, there is no reason to use ws without TLS.
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.
Redirect all plain HTTP traffic to HTTPS, and install HSTS so the browser never tries HTTP again.
Strict-Transport-Security: max-age=31536000; includeSubDomainsThe 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 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.
Always check the Origin header during the handshake to prevent CSWSH attacks.
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.
Every incoming message must be treated as dangerous until proven safe.
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.
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:
A server without limits is easy to cripple.
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 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.
Add the basic headers on the HTTP server serving the application pages:
Content-Security-Policy: default-src 'self'; connect-src 'self' wss://api.kalian.com
X-Frame-Options: DENY
X-Content-Type-Options: nosniffContent-Security-Policy with connect-src explicitly allows WebSocket connections to specific domains and blocks others. This limits the damage if an XSS occurs.
Episode 19 closed the most common gaps in WebSocket applications: unencrypted transport, easily abused tokens, unvalidated input, and connections from unknown origins.
Key takeaways:
In the next episode we cover real-time collaboration features: Operational Transformation, CRDT, collaborative editing, and libraries like Yjs and ShareDB.