Topic pattern is not a type, but it needs to be?

There was this part in phx.gen.auth.

defp put_token_in_session(conn, token) do
  conn
  |> put_session(:user_token, token)
  |> put_session(:live_socket_id, "user_sessions:#{Base.url_encode64(token)}")
end

Why Base.url_encoded64 here? So I had just cut it.

- |> put_session(:live_socket_id, "user_sessions:#{Base.url_encode64(token)}")
+ |> put_session(:live_socket_id, token)

After a while, authentication started to malfunction.

Probably because :live_socket_id is read here?

def log_out_user(conn) do
  ...
  if live_socket_id = get_session(conn, :live_socket_id) do
    DemoWeb.Endpoint.broadcast(live_socket_id, "disconnect", %{})
  end
  ...
end

Slowly and painfully, I’ve realised that the value for :live_socket_id has to be topic type. and Base.url_encode64 was needed to make it not have multiple colons which confuses the consumer of topic(). (Let me know if this is even right.)

It was very difficult for me to debug this and understand the use of Base.url_encode64. Because under the hood, @type topic() :: binary(). Nothing special. It produces silent bugs. In reality, it has to conform a specific format of topic:subtopic.

https://hexdocs.pm/phoenix/Phoenix.Socket.html#channel/3

It’s little scary that there is a required format that cannot be a type. Is it really happening? Or am I confused?

In the underlying implementation, the topic just has to be a binary, there’s no other requirement. You can just use something like “topic”, but by convention, we will use something like “topic:subtopic” to let the topic contains more structured information.


Maybe you can provides a minimal repo to reproduce this issue.

1 Like

Okay, that’s a relief. I must’ve been confused.

Then why does phx.gen.auth has Base.url_encode64 for :live_socket_id?

token is random bytes, which complicate some things:

  • some of the APIs that live_socket_id passes through expect a String.t() (for instance) which should only have valid UTF-8 bytes in it.

  • the token might contain a byte with the value 42, which will make code like channel complain

1 Like

As a supplement of @al2o3cr 's answer, the token is generated by :crypto.strong_rand_bytes/1 and :crypto.hash/2, which can’t ensure all the generated bytes are valid UTF-8 bytes.

You can try :crypto.strong_rand_bytes(30) in IEX, and checkout the result.

1 Like