LiveView and multi tenancy with Ecto foreign keys trouble

Hello all!

I’ve hit the wall with Phoenix Liveview, Ecto and multi tenancy. To make the story short(er), I’ve managed to get multi tenancy and Phoenix auth (generated with phx.auth.gen) to work in my Phoenix app by following the excellent guide at Multi tenancy with foreign keys — Ecto v3.6.1. So far, so good. The way it works is that you put a tenant id into the process dictionary and then all repo read queries in that process will be scoped to the correct tenant id. Very simple, cheap and elegant I must say!

  def fetch_current_user(conn, _opts) do
    {user_token, conn} = ensure_user_token(conn)
    user = user_token && Accounts.get_user_by_session_token(user_token)

    if user && user.org_id do
      # Put tenant id in the process dict
      Repo.put_org_id(user.org_id)
    end

    assign(conn, :current_user, user)
  end

The trouble comes when using LiveView. Since LiveView spawns a new process for every connection the tenant key is left behind in the parent process and is no longer available to Ecto repo. Now, I could put the tenant_id on the session and then later set it in the Liveview process, but that can easy get out of hand if you have many live views in your app, because you have to do it for every Liveview explicitly.

My question: Is there an idiomatic way to put (copy or merge) the tenant_id from the parent Phoenix process to the newly spawned LiveView process?

I am still learning the stack and the language. Love it, but hard things like these are currently beyond my ability.

This question contains a misunderstanding of how live view works. LiveView does NOT take a regular HTTP request and then spawn a new process for live view. Instead, there is an HTTP request, and then an HTTP response as normal to do the “static render”. At this point the HTTP request process is completely done, dead gone. Then the javascript on the front end initiates a websocket request, which the server handles in a new process from scratch to create the “live render”. This live rendering process is the one that sticks around.

Importantly, these are two entirely separate requests from the client. They might not even be handled by the same server. What this means is that tenant information needs to be derivable at the point that the liveview socket is handling the live request.

3 Likes

Thanks for the explanation. I kind of actually knew (suspected) this already when I think about it. Makes total sense.

A follow-up question. If authentication is involved, how does LiveView know about the current user? I know that you can put the current user on the Phoenix session and then take tenant id from the user or from the session in the mount hook, but that’s a little tedious if you have many live views in your app. Sure, I could put it in a LiveHelpers module, but is there a better way? I will be satisfied with a NO, just want to make sure I didn’t miss anything as I am still learning :slightly_smiling_face:

You can use the session to reference the tenant and set the pdict on mount. See the gettext example docs to see this approach in action:
https://hexdocs.pm/phoenix_live_view/using-gettext.html

2 Likes

Thank you for the answer! I suspected this was the way to do it, now I know for sure :+1: