Safeguard
Application Security•

ASVS 5.0 V4.4.3: Use Dedicated Tokens for WebSockets

If normal session handling cannot cover your WebSocket, issue a dedicated token that meets session management rules. Here is a safe short lived design.

Safeguard Security Team
4 min read

ASVS 5.0 V4.4.3 (level 2) says that if the application's standard session management cannot be used for a WebSocket, you must use dedicated tokens, and those tokens must meet the same session management security requirements as normal session tokens.

When does standard session handling not work? Native clients with no cookie jar, cross domain sockets served from a different host, and browsers where you cannot set custom headers on a WebSocket handshake. In these cases teams often pass a user id or a long lived API key in the URL. That is the pattern this requirement is meant to prevent.

Why it matters

Session tokens are bearer credentials. Whoever holds one is the user. If a socket token is guessable, never expires, or sits in a URL that gets logged by proxies and browsers, it becomes a weak side door around your main login.

Browsers cannot add an Authorization header to a WebSocket handshake. That constraint pushes developers toward query strings like ?token=..., which end up in access logs, referrer data and shared screenshots. A dedicated token designed for this job limits the damage: it is short lived, single purpose and revocable.

How to implement it

Treat the socket token as its own credential type with these properties.

  • Random and unguessable: at least 128 bits from a cryptographic generator.
  • Short lived: seconds to a few minutes, and single use where practical.
  • Bound to the user and session that requested it, and to a purpose, "websocket".
  • Revocable, and invalidated when the parent session ends or logs out.
  • Never reusable as an API token.

A simple store backed implementation in Node:

import crypto from 'node:crypto';

export async function issueSocketToken(sessionId, userId) {
  const token = crypto.randomBytes(32).toString('base64url');
  await redis.set(`wst:${token}`, JSON.stringify({ sessionId, userId }), 'EX', 30);
  return token;
}

export async function redeemSocketToken(token) {
  const key = `wst:${token}`;
  const raw = await redis.getdel(key); // single use
  if (!raw) return null;
  const { sessionId } = JSON.parse(raw);
  return (await sessionIsActive(sessionId)) ? JSON.parse(raw) : null;
}

Where the client can send it, prefer a header or the Sec-WebSocket-Protocol subprotocol field over the query string. If the query string is the only option, keep the lifetime so short and the use so singular that a logged value is worthless by the time anyone reads it. Make sure your logging redacts the parameter anyway.

How to test it

Walk through the lifecycle and try to break each rule.

  1. Request a token, connect once, and confirm it works.
  2. Reuse the same token for a second connection. It must be refused.
  3. Wait past the expiry and try again. Refused.
  4. Log out of the parent session, then try a fresh unexpired token. Refused.
  5. Present the socket token to a normal REST endpoint. It must not authenticate there.
  6. Check access logs, proxy logs and the browser history for the token value, and confirm it is redacted or already dead.

Also measure randomness by sampling a few hundred tokens and checking they share no structure.

Common mistakes

  • Putting the user id, an email or a static API key in the URL.
  • Tokens that live for hours or until the page closes.
  • The same token accepted by the REST API as a bearer credential.
  • No link to the parent session, so logout leaves open sockets and valid tokens behind.
  • Predictable values such as a timestamp plus a counter.
  • Logging full request URLs at the proxy with the token included.

Quick check

Why is a long lived API key in a WebSocket URL a bad idea?

URLs are logged and shared, and a long lived key works as a full credential. A dedicated short lived single use token limits the harm.

Sources

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.