All posts

April 19, 2026 · 5 min read

Your Chat Feature's Real-Time Connection Might Not Be Checking Who's on the Other End

Real-time features, a live chat, a collaborative document, a dashboard that updates instantly, usually run over a websocket, a connection that stays open continuously rather than the normal pattern of one request, one response. That different shape changes how authentication needs to be handled, and it's a change that's easy to miss when a feature gets built quickly.

Why a websocket is a different authentication problem

A normal API request carries proof of who's asking, a cookie, a token, with every single request, and your server can check it every time. A websocket connection is typically authenticated once, at the moment the connection opens, and then stays open, sometimes for a long time, without re-checking that authentication on every message that flows through it afterward. That's a reasonable design for performance. It also means a bug in the initial authentication check, or a connection that's mishandled after a user's permissions change mid-session, has a longer window to matter.

A concrete way this goes wrong

Picture a live chat feature where a websocket connection, once opened, joins a specific conversation's channel based on an ID passed in at connection time. If the server checks that a valid, logged-in user is connecting, but doesn't separately verify that this specific user is actually a participant in this specific conversation, any authenticated user could potentially open a connection to a channel ID that isn't theirs and start receiving messages meant for someone else's conversation entirely. The connection itself passed authentication. It just never checked authorization for the specific channel being joined.

Why this is easy to miss in normal testing

Testing a chat feature normally means joining your own conversation and confirming messages arrive correctly, which works perfectly regardless of whether the channel-level check exists. The gap only shows up if you deliberately try to connect to a channel ID that isn't yours, the same pattern as nearly every other bug on this blog: invisible during normal use, only visible when someone specifically tries the thing the feature was never meant to allow.

What to actually check on your own real-time features

  • Confirm the websocket connection itself requires real authentication at the moment it opens, not just an assumption that only legitimate clients would ever try to connect.
  • Separately, confirm that joining a specific channel, room, or conversation checks whether the connecting user actually belongs to it, the same access-control question as any other feature, just applied to a connection instead of a page.
  • Check what happens if a user's access changes mid-session, removed from a workspace, banned, permissions revoked. Does their already-open connection get closed, or does it keep receiving data on the old permissions until it naturally disconnects?
  • Test by opening a connection as one account and attempting to join a channel or room that belongs to a different account entirely.

Real-time features feel fundamentally different from a normal page, and that different feel is exactly why the same access-control question, who's actually allowed to see this, gets applied less rigorously. The connection being 'live' doesn't change who should be allowed to read what's flowing through it.

Related reading

Harbova is a security service for apps built with AI tools. Start with a free scan, and if it finds something serious, we can fix it and prove it is closed.