Pre-auditThree days of senior audit time for $1,500, credited in full toward your Zealynx audit.3 audit days, credited toward your auditSee how →
Back to Blog
WebSocket Authentication Is a Retention Boundary
PentestingWeb3 SecurityTypeScriptAudit

WebSocket Authentication Is a Retention Boundary

15 min
A WebSocket API can authenticate every connection correctly and still distribute its credentials to the wrong systems.
The problem begins before the first WebSocket frame. A connection starts as an HTTP opening handshake. If a reusable secret is placed in that handshake URL, the secret enters the request target observed by proxies, load balancers, application servers, and other telemetry layers. TLS protects the request in transit. It does not stop trusted infrastructure from recording what it receives.
This article studies that boundary through a public Dripster finding and the behavior documented by the WebSocket standard, browser API, server logging systems, Socket.IO, OWASP, MITRE CWE, and the IETF bearer-token specification. The question is narrower than "which authentication method is best?"
Which credential carrier gives the systems that need a secret access to it without giving ordinary URL telemetry the same access?
The Dripster case study contains the engagement narrative. This research removes the client name from the design problem and compares the mechanisms that remain.

Methodology and evidence boundaries

The comparison uses three evidence classes:
  1. Protocol and platform facts. RFC 6455 and the WHATWG WebSockets Standard define the opening handshake and browser interface. Product documentation describes what NGINX, Apache, AWS Application Load Balancer, and Socket.IO record or expose.[2][3][7][8][9][10]
  2. Security guidance. RFC 6750, OWASP, and MITRE CWE-598 describe the risks of carrying bearer credentials or other sensitive data in URI query parameters.[4][5][6]
  3. Anchor finding. Zealynx finding F-2026-0003 records one reviewed application that accepted partner API keys in WebSocket query parameters and later removed that carrier.[1]
The carrier taxonomy and design conclusions are Zealynx classifications derived from those sources. They are not protocol requirements or claims that every implementation has identical exposure.
This is a mechanism-led comparison, not a prevalence study. It does not measure how often WebSocket credentials leak, inspect any production log, or establish exploitation. Product defaults can be changed, headers can also be logged, and retention depends on each deployment's configuration.

The handshake crosses more systems than the socket

RFC 6455 defines WebSocket as an opening handshake followed by message framing. Its client handshake example uses an HTTP GET request with a resource name and upgrade headers.[2]
The WHATWG standard makes the URL relationship explicit. It constructs the WebSocket resource name from the URL path and appends the query when one exists. It then creates a handshake request from that URL.[3]
That means this connection:
1wss://stream.example/v1/positions?apiKey=LONG_LIVED_SECRET
starts with a request target containing the secret:
1GET /v1/positions?apiKey=LONG_LIVED_SECRET HTTP/1.1
2Upgrade: websocket
3Connection: Upgrade
TLS encrypts those bytes between network peers. Once the request reaches a terminating load balancer, reverse proxy, or application server, that component can process the request target in plaintext. Whether it persists the value is a separate logging decision.
This distinction is the core security model:
1transport confidentiality
2 is not the same property as
3credential minimization inside trusted infrastructure
OWASP states that HTTPS does not resolve sensitive-data exposure through query strings and lists web logs, browser history, browser cache, shared systems, and referrer data as possible retention surfaces.[5] MITRE CWE-598 similarly notes that query strings can be saved in browser history, passed through referrers, stored in web logs, or otherwise recorded, and recommends request bodies or headers instead.[6]
Not every listed surface applies equally to every WebSocket client. For example, the WHATWG handshake uses a no-referrer request policy.[3] The durable point is not that every URL reaches every surface. It is that the URL is an ordinary operational identifier, while a reusable credential needs a narrower handling boundary.

The Dripster anchor: authentication succeeded too early

The public Dripster finding records two authenticated WebSocket gateways that accepted a partner API key from client.handshake.query.apiKey. The extraction logic checked the query carrier before an Authorization header, and public integration guidance recommended the query form.[1]
The security defect was not an authentication bypass. A valid API key still had to be supplied. The failure was that a long-lived secret crossed systems whose ordinary role was request routing and telemetry.
Zealynx classifies this as correct authentication with an over-broad credential path:
1valid credential
2+ valid TLS connection
3+ valid server-side verification
4+ URL-retaining infrastructure
5= authentication succeeds, but secret distribution expands
The public resolution says query-string support was removed and the auth payload or Authorization header became required.[1] Removing support matters more than changing precedence. If the backend merely prefers a header but still accepts ?apiKey=, copied examples and older clients can continue using the unsafe path.
The finding is marked fixed. That source status does not prove deployment timing, credential rotation, prior log contents, exploitation, or the security of later versions.

Comparative evidence: request URLs are built to be observed

The logging documentation provides a useful cross-check because it shows how ordinary infrastructure treats request targets.

NGINX

NGINX documents a predefined combined access-log format containing $request. Its example format records the request line along with status, response size, referrer, and user agent.[7]
A query secret in the request target can therefore enter a common access-log configuration. This is a documented capability and default format, not evidence about any particular deployment.

Apache HTTP Server

Apache documents Common Log Format with %r, the first line of the request. Its example output includes the HTTP method, requested resource, and protocol.[8]
Again, the relevant property is structural. The request target is useful operational data, so routine logging can preserve it without any authentication component deliberately logging a credential.

AWS Application Load Balancer

AWS documents a request_line field that records the method, protocol, host, port, URI, and HTTP version. It says the load balancer preserves the URL sent by the client when recording the request URI. AWS also documents ws and wss access-log entry types and notes that a WebSocket entry is written after the connection closes.[9]
This is the clearest managed-infrastructure example in the comparison. A secure WebSocket still has a logged request line when access logging is enabled. The safe conclusion is not to disable operational logging. It is to keep reusable credentials out of the field that logging is designed to retain.

Why browser clients make the design awkward

The browser WebSocket constructor accepts a URL and an optional list of subprotocols. It does not expose a general request-header argument.[10] The WHATWG interface definition confirms the same constructor shape.[3]
This constraint explains why query authentication keeps appearing. Server-to-server clients often can attach an Authorization header to the upgrade request. A browser using the native API cannot simply copy that pattern.
Convenience, however, does not change the retention model. It changes the architecture required to preserve it.
Socket.IO demonstrates one library-specific alternative. Its documentation exposes an auth option on the client and socket.handshake.auth on the server. Middleware can evaluate those credentials before the Socket.IO connection is accepted.[11] This is not a feature of raw browser WebSocket, and Socket.IO is not wire-compatible with every WebSocket service. It is evidence that an authentication payload can be separated from a URL when the application protocol supports it.
The useful design question is therefore client-specific:
1Can this client set protected handshake headers?
2If not, can the application protocol authenticate before privileged messages?
3If not, can a long-lived credential be exchanged for a short-lived, narrow ticket?

A carrier taxonomy for WebSocket credentials

The table below is a Zealynx classification. "Reduced URL exposure" does not mean "secret" by itself. Every carrier still needs TLS, redaction, scoped access, rotation, and explicit failure behavior.
CarrierBrowser-native availabilityURL retention exposurePrincipal design condition
Long-lived key in queryYesDirectReject for reusable secrets because the request target carries the key
Authorization headerNot through the native browser constructor; commonly available to non-browser clientsReducedRedact sensitive headers and authenticate before upgrade acceptance
CookieIncluded by browser handshake rules when applicableReducedEnforce Origin policy and address cross-site request risks, scope, and cookie attributes
Socket.IO auth payloadAvailable to Socket.IO clientsReducedValidate in connection middleware and do not authorize application events before success
First WebSocket messageAvailable after raw WebSocket opensReducedKeep the connection strictly unauthenticated until validation and close on timeout or failure
Short-lived single-use ticket in queryYesTicket still enters URL logsMake it narrow, rapidly expiring, one-time, and useless after redemption
Secret in Sec-WebSocket-ProtocolBrowser API can propose subprotocol valuesReduced URL exposure, but may enter header logsAvoid overloading protocol negotiation with credentials unless the complete logging and interoperability model is controlled

Authorization header

RFC 6750 specifies the Authorization request header as the normal bearer-token method and says resource servers must support it for OAuth bearer tokens.[4] The RFC is about OAuth, not API keys or WebSocket specifically, but its carrier analysis is relevant to bearer-like credentials.
Headers are not magically unobservable. Application traces, debug middleware, packet captures after TLS termination, or deliberately verbose proxy logs can retain them. The advantage is that authentication headers have clear semantics and can be redacted by policy without destroying the operational value of the request URL.

Cookie

The WHATWG handshake uses credentials mode include, so cookies can participate in browser WebSocket handshakes.[3] This avoids putting a session identifier in the URL, but it introduces a different boundary: browsers can attach cookies automatically.
A cookie-authenticated WebSocket service must validate the Origin header against an allowlist and apply appropriate cookie scope and attributes. Cookie transport solves the URL problem only. It does not by itself solve cross-site WebSocket initiation or authorization.

First-message authentication

A raw browser WebSocket can send a credential as its first application message after the connection opens. This keeps the credential out of the handshake URL, but the server has accepted a transport before authenticating it.
A safe state machine needs at least:
1CONNECTING -> OPEN_UNAUTHENTICATED -> AUTHENTICATED
2 | |
3 +-> CLOSED <-------+

Working auditors in your corner, all year

Zealynx Insiders: weekly live sessions, 1:1 advisory, pair-auditing, and Krait runs on your code, from an audit firm that publishes its reports.

While in OPEN_UNAUTHENTICATED, the connection must not subscribe to private streams, execute commands, allocate expensive tenant resources, or receive sensitive data. It also needs a short authentication deadline and message-size limits.

Single-use ticket

RFC 6750 says bearer tokens should not be passed in page URLs because URLs are likely to be logged, and it does not recommend the URI query method unless other methods are impossible.[4] A single-use ticket does not make URL logging disappear. It limits what a retained URL value can do.
A robust ticket exchange binds the ticket to a narrow purpose, short expiry, intended tenant or user, and one successful redemption. The server consumes it atomically. Replay then fails even if the URL appears later in a log. This is risk reduction, not permission to place the long-lived API key in the ticket field.

The reusable invariant

The comparison supports a practical invariant:
A reusable WebSocket credential must not cross an observability surface merely because that surface needs to route the opening handshake.
This can be expressed as a trust-boundary check:
1systems receiving reusable credential
2 subset of
3systems responsible for authentication
When a URL contains the credential, the set often expands toward:
1edge routing
2load balancing
3access logging
4analytics ingestion
5support tooling
6log archives
The exact set is deployment-specific. That is why a security review must trace actual infrastructure rather than stop at application code.

Testing the boundary

A useful WebSocket authentication review combines code, configuration, documentation, and runtime behavior.
  1. Enumerate every carrier. Search query parsing, headers, cookies, connection payloads, subprotocol values, and first-message handlers.
  2. Test conflicting carriers. Send different credentials in each accepted location. Ambiguous precedence can produce surprising identity selection.
  3. Reject forbidden carriers. Assert that a valid long-lived key in the query is refused, even when no safer credential is present.
  4. Inspect request telemetry safely. Use synthetic non-secret markers to determine which request fields reach proxy, load-balancer, application, trace, and analytics records.
  5. Review examples and SDKs. Documentation is part of the effective interface. If examples use an insecure carrier, integrations will reproduce it.
  6. Verify unauthenticated state. For first-message designs, attempt subscriptions and commands before authentication, after failed authentication, and after timeout.
  7. Test replay. Redeem a ticket twice, concurrently and sequentially. Only one attempt should succeed.
  8. Test revocation and rotation. A potentially exposed key needs bounded scope and an operational path to replacement.
  9. Check redaction. Confirm that authentication headers and payloads are excluded from normal logs and error reports.
  10. Test Origin policy. Browser-accessible cookie authentication requires explicit accepted origins rather than trust in connection possession alone.
A useful regression for the Dripster class is deliberately boring:
1it("rejects reusable API keys in the WebSocket query", async () => {
2 const result = await connect("wss://stream.example/v1?apiKey=TEST_MARKER");
3
4 expect(result.accepted).toBe(false);
5});
The marker should be synthetic. A security test does not need a production credential to prove that the carrier is accepted.

What this research does not establish

This comparison does not establish that every proxy logs query strings, every header is redacted, or every browser stores a WebSocket URL in the same way. Logging formats are configurable. Managed-service features can be disabled. Applications can add their own traces.
It also does not claim that the Dripster key was found in a production log, accessed by another party, replayed, or used to change positions. The public finding establishes an exposure path and a recorded remediation, not a realized incident.[1]
RFC 6750 concerns OAuth bearer tokens. Applying its query-carrier reasoning to API keys is a mechanism-level analogy because both credential types can grant access to whoever possesses them. It does not make the reviewed API key an OAuth token.
Socket.IO is an application framework layered over its own transport behavior. Its auth payload is evidence for one design option, not a standard feature of the native browser WebSocket API.
Finally, a header, cookie, payload, or ticket can be implemented badly. Moving a credential out of the URL narrows one retention path. It does not replace authorization, tenant isolation, Origin validation, rate limits, replay protection, revocation, or secure secret storage.

Conclusion

WebSocket authentication is not complete when the server can answer "is this key valid?" It must also answer "which systems saw the key before we checked it?"
The protocol begins with an HTTP request. Common infrastructure records HTTP request targets because they are valuable for operations. That makes a URL a poor carrier for a reusable secret, even over wss://.
The strongest fix is structural: remove the long-lived query carrier rather than warn clients away from it. Use an authentication-specific header where the client supports one. For browser constraints, choose a deliberately gated auth payload, a carefully protected cookie flow, or a short-lived single-use ticket. Then test the unauthenticated state and the telemetry boundary, not only the happy-path connection.
For the engagement context behind the anchor finding, read Dripster: Keeping API Keys Out of WebSocket URLs. If your application connects off-chain APIs to on-chain value, Zealynx can review the handshake, authorization model, logging boundary, and downstream actions through an application security assessment.

FAQ

1. Does WSS make API keys in query strings safe?
No. WSS protects the handshake in transit, but infrastructure that terminates TLS receives the request URL. Access logs and application telemetry can then retain the query string.[5][7][9]
2. Can browser WebSocket clients set an Authorization header?
The native browser constructor exposes a URL and optional subprotocols, not a general headers option.[3][10] Libraries and non-browser clients can offer different interfaces, so the design must match the actual client.
3. Is first-message authentication secure?
It can avoid URL exposure, but the socket opens before authentication. The server must maintain an explicit unauthenticated state, block privileged actions, limit resources, apply a deadline, and close on failure.
4. Is a single-use query ticket always safe?
No. The ticket can still be logged. Short expiry, narrow scope, atomic one-time redemption, TLS, and replay testing limit the consequence of retention. A reusable API key should not be used as the ticket.
5. What should an audit test besides the credential carrier?
Review access control, tenant authorization, Origin validation, replay behavior, revocation, rate limits, documentation examples, and the complete attack surface around proxies and telemetry.

Glossary

TermDefinition
Access ControlRules that determine which authenticated principal may perform each action or read each resource.
Attack SurfaceThe complete set of reachable interfaces and trust boundaries available to an attacker.
Replay AttackReuse of captured valid data to repeat an authenticated or value-changing action.
Threat ModelingA structured process for identifying assets, trust boundaries, threats, and controls.

Sources

[1] https://zealynx.io/audits/dripster/offchain-pentest-q2-2026/findings/F-2026-0003 - Zealynx, API Key Exposed in WebSocket Query Parameters [2] https://www.rfc-editor.org/rfc/rfc6455 - IETF, The WebSocket Protocol [3] https://websockets.spec.whatwg.org/ - WHATWG, WebSockets Standard [4] https://www.rfc-editor.org/rfc/rfc6750 - IETF, OAuth 2.0 Bearer Token Usage [5] https://owasp.org/www-community/vulnerabilities/Information_exposure_through_query_strings_in_url - OWASP, Information exposure through query strings in URL [6] https://cwe.mitre.org/data/definitions/598.html - MITRE, CWE-598: Use of HTTP Request With Sensitive Query String [7] https://nginx.org/en/docs/http/ngx_http_log_module.html - NGINX, HTTP log module [8] https://httpd.apache.org/docs/2.4/logs.html - Apache HTTP Server, Log Files [9] https://docs.aws.amazon.com/elasticloadbalancing/latest/application/load-balancer-access-logs.html - AWS, Application Load Balancer access logs [10] https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/WebSocket - MDN, WebSocket constructor [11] https://socket.io/docs/v4/middlewares/ - Socket.IO, Middlewares and sending credentials

Working auditors in your corner, all year

Zealynx Insiders: weekly live sessions, 1:1 advisory, pair-auditing, and Krait runs on your code, from an audit firm that publishes its reports.