Using WebSockets With Cookie-Based Authentication

Tiago Duarte
May 11 · 6 min read

Although only my name appears as the owner of this article it was created together with my old friend 👴🏽 Nuno Marinho and, as usual, with lots of inputs from the whole Coletiv team.

Using WebSockets With Cookie-Based Authentication

Before diving into the details of the problem lets first have a small recap about these two topics: WebSockets and cookie-based authentication.

WebSockets

According to the MDN web docs:

The WebSocket API is an advanced technology that makes it possible to open a two-way interactive communication session between the user’s browser and a server. With this API, you can send messages to a server and receive event-driven responses without having to poll the server for a reply.

Long days are gone since webpages were just a mixture of text and hyperlinks. Feeling nostalgic? 👵🏼 You can check here the first webpage ever built.
With the evolution of the world wide web, and computers & smartphones in general, we started seeing richer experiences. Ajax came into play and webpages started to interact with servers without the need for a full reload.
Well, evolution never stops 🤘🏽! And we are once again seeing webpages morphing into (near) real-time experiences.
WebSockets, by keeping a two-way communication channel, allow servers to continuously push information/content into the webpage without the need for expensive mechanisms like long-polling.

As a side note, these concepts also apply to mobile applications as well. It is just that it is funnier to look at the early infancy of the web and compare it to its current status.

Cookie-based authentication

Cookie-based authentication has been the default, battle-tested method for handling user authentication for a long time.
Cookie-based authentication is stateful. This means that a record or session is kept both server (optional) and client-side.
The server can, optionally, keep track of active sessions. While on the front-end a cookie is created that holds a session identifier, thus the name cookie-based authentication.

Let’s look at the flow of traditional cookie-based authentication:

  1. A user enters their login credentials
  2. The server verifies the credentials are correct and creates a session which is then stored (e.g.: database)
  3. A cookie with the session ID is placed in the user’s browser
  4. On subsequent requests, the session ID is verified against the database and if valid, the request is processed
  5. Once a user logs out of the app, the session is destroyed both client-side and server-side
Cookie-based authentication flow

Source: Dzone

Problem: How do we authenticate Elixir GraphQL subscriptions? 🤔

According to the documentation:

Subscriptions are a GraphQL feature that allows a server to send data to its clients when a specific event happens. Subscriptions are usually implemented with WebSockets. In that setup, the server maintains a steady connection to its subscribed client.

GraphQL subscriptions on their own are quite easy to implement. But, in our case, the subscription could only be done if the user was authenticated, which was something we haven’t done before.

Like most developers, we started looking for solutions first on the Apollo docs, then on the Craft GraphQL APIs in Elixir with Absinthe book.
Problem was that both cases, as well as most of the articles on the web, were pointing towards token-based auth. Or so we thought 🤦🏼‍♂️, as we were blindly 🙈 looking for explanations on how to do it using cookie-based authentication.

Solution 💡

Although, in theory, one could use cookies, as all WebSocket connections start with an HTTP request (with an upgrade header on it), and the cookies for the domain you are connecting to, will be sent with that initial HTTP request to open the WebSocket. It is not recommended as WebSockets are not restrained by the same-origin policy.

Lifetime of a WebSocket connection

Using cookies could actually leave users vulnerable to cross-site scripting attacks (xss).
There is a very good article on Cross-Site WebSocket Hijacking (CSWSH) that lead us to a solution:

As you’ve already noticed, securing an application against Cross-Site WebSocket Hijacking attacks can be performed using two countermeasures:

1. Check the Origin header of the WebSocket handshake request on the server, since that header was designed to protect the server against attacker-initiated cross-site connections of victim browsers!

2. Use session-individual random tokens (like CSRF-Tokens) on the handshake request and verify them on the server.

Actually, this is exactly what Phoenix Live View does.
When using live view there is an initial webpage render that contains a <meta> tag with the a csrf token. We then read the token via javascript, and send it via params to create the WebSocket connection:

Live View connect using javascript

Possible implementation

We won’t get into the details on how to fully setup and configure subscriptions on a project, you can follow the official Absinthe subscription documentation for that.
Besides the usual setup we did the following:

1. Create a subscription that expects the user_count:#{user_id} as a topic that we want to subscribe to:

2. Change me endpoint, to allows the user to get a token containing his user_idthat will allow us to authenticate the subscription and make sure a user can only subscribe to its own data:

3. Create a handle connect function on the socket. The function expects a token to be sent, validates that the user_id contained inside the token belongs to the current user so that the current user can only subscribe to data from himself:

4. For the sake of this example and to test our solution, we created a Worker that every 10 seconds publishes to all user topic possibles a random number:

Worker publishing random number to all users

To test the solution you can follow the readme:

  1. Install the dependencies with mix deps.get
  2. Create and migrate the database with mix ecto.setup
  3. Start the Phoenix server with iex -S mix phx.server
  4. Open the GraphiQL Interface and import this workspace.
  5. Run the mutation - accountsLogin
  6. Run the query - accountsMe and copy the token returned
  7. Change the ws url token on subscription - accountsUserCount and run the query
  8. Verify accountsUserCount value changing every 10 seconds on the result panel

Final notes

Sources

Thank you for reading!

In case you don’t know, Coletiv is a software development studio from Porto specialized in Elixir, iOS, and Android app development. But we do all kinds of stuff. We take care of UX/UI design, web development, and even security for you.

So, let’s craft something together?

Coletiv

Thoughts, dreams and rants about technology and work life…

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store