Safeguard
Application Security•

How do you build a secure WebSocket server in Python?

Use wss, check the Origin header, authenticate before sending data, authorize each message and cap connections and message size. Details from RFC 6455.

Safeguard Security Team
5 min read

A secure Python WebSocket server uses TLS (wss), validates the Origin header against an allowlist, authenticates the client before sending protected data, checks authorization on every message, and caps connections, message size and memory. The protocol itself provides no login, so each of these is your code's job.

What does the protocol give you, and what not?

RFC 6455 defines the WebSocket protocol. On authentication, it says the protocol does not prescribe any particular way for servers to authenticate clients during the handshake, and that a server can use any mechanism a generic HTTP server can, such as cookies, HTTP authentication or TLS authentication. On confidentiality, it says integrity and confidentiality come from running over TLS, with wss URIs, and that implementations must support TLS and should use it.

So the baseline is two decisions you must make yourself: encrypt the connection, and decide how clients prove who they are.

How do you stop cross-site hijacking?

Browsers attach cookies to a WebSocket handshake. That is convenient, and dangerous. The OWASP WebSocket Security Cheat Sheet describes cross-site WebSocket hijacking: a user logs in, later visits a malicious site, the malicious page opens a WebSocket to your server, the browser sends the user's cookies automatically, and the server accepts it. The attacker's page then has a live, authenticated connection.

The defense in RFC 6455 is the Origin header. It says servers that are not intended to process input from any web page, but only from certain sites, should verify the Origin field is one they expect, and respond with HTTP 403 if not. OWASP agrees and adds: use an explicit allowlist, not a denylist, and avoid wildcards or substring matching.

In Python's websockets library, you can check the header during the handshake. Here is a constructed sketch:

import asyncio
import websockets

ALLOWED = {"https://app.example.com"}

async def handler(ws):
    # the first message must carry a token; reject anything else
    ...

async def main():
    async with websockets.serve(handler, "127.0.0.1", 8765, origins=list(ALLOWED)):
        await asyncio.Future()

Check the library's current documentation for the exact parameters in your version, since options change between releases. Other protections OWASP lists include SameSite cookies and CSRF tokens in the handshake.

How should authentication work?

OWASP advises, for browser clients using tokens, sending the token in the first message over WSS and not in the URL query string, because URLs leak into access logs. Until validation succeeds, accept only the authentication message and send no protected data. Close the connection on failure or timeout, and limit the number of unauthenticated connections.

Long-lived connections create a second problem: sessions expire while sockets stay open. OWASP recommends closing connections when sessions expire, re-validating periodically, and closing every connection for a user at logout. Keep a map from sessions to open sockets so you can do that quickly.

What about authorization and input?

A connected socket is not permission for everything. Check authorization for each message and each action, as you would for each HTTP request. Treat message bodies as untrusted input: validate against a schema, limit size and reject unknown message types. OWASP warns that messages can carry injection payloads just like any other input.

How do you resist denial of service?

The websockets documentation is direct about the risk. It warns that an attacker who can open an arbitrary number of connections can cause denial of service by memory exhaustion, and says that with default settings a connection uses about 70 KiB of memory. Highly compressed messages can use much more, up to 128 MiB with a 1000 times amplification between network traffic and memory. Its guidance: reject suspicious connections before they reach the library, typically in a reverse proxy, and configure the server to reduce memory use. It also documents limits on handshake size: an 8192 byte request line, 8192 bytes per header and 128 headers at most.

OWASP also advises disabling permessage-deflate compression unless needed, because compression combined with secret data can leak information, and setting proper timeouts on proxies.

How to check or apply this

  1. Serve only over wss, with TLS at a reverse proxy or in the server.
  2. Allowlist exact origins and return 403 for others.
  3. Authenticate with a first-message token, never in the URL, and close on failure.
  4. Re-check sessions during long connections and close on logout.
  5. Authorize every message by action and resource, and validate each against a schema.
  6. Cap connections per client, message size and memory, and put a proxy in front.
  7. Scan the Python dependencies of the server in CI, so a vulnerable package does not undo the rest.

Quick check

Does wss stop cross-site WebSocket hijacking?

No. TLS protects the connection from eavesdropping. Hijacking comes from the browser sending cookies to your server, so you need an Origin check and CSRF protection.

Where should the auth token go?

OWASP recommends the first message over WSS for browser clients, not the URL query string, because URLs can end up in access logs.

Sources

Last reviewed: 18 October 2026.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.