Skip to content
Ashish.
All posts
Diagram illustrating the WebSocket handshake and authentication lifecycle.
6 min readBackendBackend Developers, Full-Stack DevelopersFeatured#websocket#authentication#oauth#jwt#security#real-time#backend

WebSocket Authentication: OAuth, JWT, or Something Else

Explore WebSocket authentication strategies including OAuth, JWT, and handshake protocols to secure real-time connections effectively.

By Ashish Kumar

Securing websocket authentication is often misunderstood as simply "adding auth to a socket." In reality, it is a lifecycle problem. Unlike HTTP, where every request carries its own authentication context, a WebSocket connection begins with an HTTP handshake and then transitions into a full-duplex binary or text stream for real-time communication. Once upgraded, the HTTP headers are gone. The server must decide when to verify identity: at the moment of handshake, or on every single message frame.

For backend developers, the choice between OAuth, JWT, and ticket-based systems isn’t about preference; it’s about where you place the burden of verification and how you handle session revocation.

The Handshake Problem: Why HTTP Auth Doesn’t Just Work

The WebSocket protocol (RFC 6455) starts as an HTTP request. The client sends an Upgrade: websocket header, and if the server agrees, the TCP connection remains open, but the HTTP conversation ends.

GET /ws/chat?token=abc123 HTTP/1.1
Host: example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13

This creates a security boundary. You cannot rely on standard HTTP Basic Auth headers being present in subsequent data frames. The only window to authenticate the client is during this initial handshake.

There are two primary mechanisms for injecting credentials here:

  1. Query Parameters: Appending ?token=... or ?sessionId=... to the URL.
  2. Custom Headers: Including a header like Authorization: Bearer ... in the initial HTTP request.

While custom headers are cleaner, many reverse proxies (like NGINX or Cloudflare) strip unknown headers before they reach your application logic, or they log URLs but not headers. Query parameters are universally supported but have a fatal flaw: they are logged in server access logs, browser history, and referer headers. If you use a token in a URL, you are leaking that credential. When evaluating oauth vs websocket strategies, one must consider these logging and proxy limitations carefully.

JWT: The Stateless Bearer Token

JSON Web Tokens (JWT) are popular because they are self-contained. The server doesn’t need to query a database to know who the user is; it just decodes the signature.

How it works in WebSockets

Implementing a websocket jwt strategy requires careful handling of the handshake, typically passing the token in the query string due to proxy limitations or as the first message frame.

// Client-side: Passing JWT in query string
const ws = new WebSocket('wss://api.example.com/ws?jwt=' + accessToken);

The server receives the handshake, extracts the token, verifies the signature using the shared secret or public key, and if valid, accepts the connection. A common websocket jwt pattern involves passing the token in the query string, though this exposes the token in logs if not handled properly.

The Mechanism Flaw: Revocation and Performance

JWTs are stateless, which is great for scaling but terrible for security control.

  1. No Immediate Revocation: If a JWT is stolen, it remains valid until it expires. You cannot "log out" a specific JWT without maintaining a blacklist (which defeats the stateless purpose).
  2. CPU Overhead: If you choose to validate the JWT on every incoming message frame (not just the handshake), you are performing cryptographic signature verification thousands of times per second. This is computationally expensive and unnecessary if the handshake was secure.

Opinion: JWTs are suitable for WebSocket auth only if the tokens have very short lifespans (e.g., 5-15 minutes) and you are okay with the latency of signature verification on the handshake. They are rarely ideal for long-lived sessions.

OAuth 2.0: The Access Token Provider

Understanding the difference in oauth vs websocket contexts is crucial. OAuth does not authenticate WebSockets directly. OAuth 2.0 (RFC 6749) is a framework for delegating authorization. It provides an Access Token. That Access Token is then used to authenticate the WebSocket connection.

The Flow

  1. User logs in via a standard OAuth provider (Google, GitHub, etc.).
  2. Your backend exchanges the code for an Access Token.
  3. Your backend generates an internal session or JWT for the user.
  4. The client uses that internal credential to open the WebSocket.

If you are building a public API where third-party apps connect to your WebSocket server, you might pass the OAuth Access Token directly in the handshake URL. However, this exposes your API to any token holder until expiry. Most enterprise backends use an intermediate layer (see Ticket-Based below) to wrap the OAuth token.

Ticket-Based Authentication: The Backend Standard

For most internal applications, the most robust mechanism is a Ticket-Based System (often implemented via Session Cookies). This ticket-based approach separates identity from connection state.

The Mechanism

  1. Login: User logs in via HTTP POST. The server validates credentials and creates a session in Redis or a database.
  2. Cookie Set: The server sets a HttpOnly cookie (e.g., session_id=xyz).
  3. Handshake: The browser automatically sends this cookie with the WebSocket upgrade request.
  4. Validation: The server checks the session_id against the session store.
    • If valid: The server generates a unique socketId and associates it with the session. The connection is accepted.
    • If invalid: The handshake is rejected with a 401 error.

The handshake validation checks the session ID to ensure the user is still active.

GET /ws HTTP/1.1
Host: example.com
Cookie: session_id=a1b2c3d4e5
Upgrade: websocket
...

Why This Wins for Most Apps

  • Instant Revocation: If a user is banned or changes password, you delete the session in Redis. The next WebSocket frame fails because the server no longer recognizes the session_id.
  • No Token Leakage: The session ID is in an HttpOnly cookie, invisible to JavaScript and not logged in URLs.
  • Performance: The server only validates the session once at handshake. Subsequent messages are authenticated by checking if the socketId is still active in memory.

Comparison: Trust, Data Flow, and Dependencies

FeatureJWT (Bearer)OAuth Access TokenTicket/Session Cookie
Verification PointHandshake (or every frame)HandshakeHandshake
StateStatelessStateless (Token) / Stateful (Introspection)Stateful (Session Store)
RevocationSlow (Blacklist/Expiry)Slow (Blacklist/Expiry)Instant (Delete Session)
Security RiskToken leakage in URL/LogsToken leakage in URL/LogsCookie theft (XSS)
Best ForMicroservices, Public APIsThird-party integrationsInternal Apps, SaaS

Recommendations

  1. Use TLS (WSS): This is non-negotiable. WebSocket over plain HTTP is a security vulnerability. All tokens, cookies, and messages are encrypted.
  2. For Internal Services: Use Ticket-Based authentication with HttpOnly cookies. It offers the best balance of security (revocation) and performance.
  3. For Public APIs: Use JWTs with short expiration times. If you must use URLs, ensure your proxy strips query parameters from access logs.
  4. For Third-Party Integrations: Use OAuth 2.0 Access Tokens passed in the handshake, but implement an introspection endpoint to allow revocation if needed.

The mechanism of trust in WebSockets is established at the handshake. Once that door is open, the server should assume the client is authenticated and focus on authorizing specific actions within the message payload, rather than re-validating identity on every frame.

Conclusion

Securing WebSockets requires recognizing that the protocol is not a single authentication event, but a lifecycle. By understanding the constraints of the HTTP upgrade handshake, developers can choose between stateless JWTs, delegated OAuth tokens, or stateful ticket-based sessions based on their specific needs for revocation speed, performance, and integration complexity.

Related posts