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.

Image for post
Image for post
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
Image for post
Image for post
Cookie-based authentication flow

Source: Dzone

Problem: How do we authenticate Elixir GraphQL subscriptions? 🤔

In a new Elixir based backend we are working on, we created a GraphQL based API and used a cookie-based authentication.
Nothing new up to this point and everything was going according to plan until we decided to provide a richer experience to the user by having the server push data directly to the page. As we were using GraphQL, subscriptions were the obvious choice.

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 💡

We were missing something basic: Secure WebSockets (WSS) use a different protocol than regular based connections which use HTTPS.

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.

Image for post
Image for post
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

So to prove our concept of “how to authenticate subscriptions in a cookie-based authentication system”, we created a small based backend elixir project.
The project is very simple, it contains a user table and all the necessary endpoints (login, logout, register, and me) to perform user registration and authentication via API using cookies with Absinthe GraphQL.

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

The problem with the csrf token solution is that the token gets sent as a query string value when the WebSocket handshake takes place.
We are not worried about man-in-the-middle attacks as the connection is made securely by using wss.
The problem is that query string values are often stored in log files on servers and that potentially means we have a log file full of authentication tokens that can be reused, which is a security risk.
Please do not forget to check your loggers and take measures so that they do not store these parameters or even these requests at all.

Sources

Thank you for reading!

Thank you so much for reading and if you enjoyed this article make sure to hit that 👏👏 button. It means a lot to us! Also don’t forget to follow Coletiv on Medium, Twitter, and LinkedIn as we keep posting more and more interesting articles on multiple technologies.

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…

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

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