Absinthe subscription and authenticating current_user

Hello, I’m implementing GraphQL subscriptions but I am missing some fundamental understanding I think as to how it’s overall supposed to work :sweat_smile:

I have this middleware called “authorize” ensuring the current_user exists (in other words, ensuring the user is authenticated).

  def call(resolution, _) do
    case resolution.context do
      %{current_user: current_user} ->
        resolution

      %{conn: conn} ->
        Absinthe.Resolution.put_result(
          resolution,
          {:error, message: "unauthorized", code: "UNAUTHENTICATED"}
        )
    end
  end

But when working with web sockets, in the context I have only:

context: %{pubsub: MyAppWeb.Endpoint}

and consequently I have no matching clause…

When working with http requests, the requests go through route pipelines, calling different plugs and one adding the current user into the conn.

But when working with websockets, all these router pipelines are not called and I have no current_user and conn struct.

I’m quite lost. Any guidance is welcome :open_hands:

Hey @thojanssens1. When working with websockets you should use the connect callback in your socket module to set the current user. The current user can be sent to the socket either as a parameter from the front end, or by accessing the cookie in the connection info.

1 Like

I use cookies. I’m using Absinthe.GraphqlWS and it seems that their handle_init callback doesn’t give me the connection info to access session/cookie data?
https://hexdocs.pm/absinthe_graphql_ws/Absinthe.GraphqlWS.Socket.html#c:handle_init/2

The graphql-ws/PROTOCOL.md at master · enisdenjo/graphql-ws · GitHub message has a payload field. You should pass in a token from your client in the payload to authenticate the socket.

The payload field is passed through to Absinthe.GraphqlWS.Socket — AbsintheGrahqlWS v0.3.3 There you can verify it and set a current_user on the absinthe context.

Example for the JS client

const client = createClient({
  url: 'ws://localhost:4000/graphql',
  connectionParams: {token: "abcdef"}
});

See graphql-ws/client.ClientOptions.md at master · enisdenjo/graphql-ws · GitHub for docs.

That will work only if the authentication mechanism implies a token stored into the client (typically localStorage)?

However, the current user is stored in a HttpOnly cookie (which is recommended over storing a token into localStorage when possible). As it is httpOnly, the JavaScript client doesn’t even have access to the content (thus not being able to pass it as payload it seems).

In the Phoenix.Socket.connect/3 the third argument is passed the connection_info map. This would allow you to extract this information.

This is however not exposed in the Absinthe.GraphqlWs library afaict. The connection is always established and authentication happens through the connection_init message.

This would restrict Absinthe.GraphqlWS to work with tokens on the client :frowning: Do you think it’s possible to change the library to allow authenticating with cookies?

Yes should be possible. I wrote the Absinthe library to support the subscriptions-transport-ws protocol. This is the older websocket graphql protocol that’s no longer supported in the new clients. It does support a custom connect/3 callback in the socket module.

You can add similar functionality in the Absinthe.GraphqlWS so the connect/2 function calls a custom module.connect/3.

1 Like

After digging in the source code, I found out that the :connect_info field is inside the Absinthe.GraphqlWS.Socket struct (while with Phoenix Socket it is received as a separate function argument in connect/3). We opened a PR in the Absinthe.GraphqlWS repo to clarify this.

I have then one remaining question, in the code above, if the user is authenticated it will now match the map %{current_user: current_user}, but if the user is not authenticated, what should I do in case of WebSockets?

For now there will be a clause matching error, as we don’t match the map %{conn: conn}. In case of http requests, a GraphQL error is sent with code “UNAUTHENTICATED”, which the JS frontend code can read and handle.

But I just don’t know what I’m supposed to do at all when working with WebSockets and unauthenticated users or any other error at all, any guidance is welcome :sweat_smile: