RobertoSchneiders

RobertoSchneiders

User authentication across multiple tabs with LiveView

I’ve been reading about session management on Phoenix LiveView for the last couple of days and can’t seem to find a solution for this problem.

My app is basically entirely built with live views, almost all redirects are live redirects. Most views can be accessed whether you are logged in or not, similar to an e-commerce app. I have a basic authentication system generated by the mix phx.gen.auth. The login is a post HTTP request (so we can set the session cookie).

When a user logs in, ideally, the other tabs that are opened in the same browser would notice that and act accordingly. Another option would be to update the session state of those tabs once the user does an action, e.g. click on a link and navigate to another view. The problem is that everything is a live redirect so the other tabs don’t update the session state unless the user refreshes the page, which is not a great user experience.

Does anyone have any idea on how to solve this?

I’m wondering if it would be possible to force a full page reload on the other tabs when logging in.

Most Liked

thomas.fortes

thomas.fortes

Ok, did a proof of concept.

Global hook at app.html.heex

<main id="main" phx-hook="MainHook" class="px-4 py-20 sm:px-6 lg:px-8">
  <div class="mx-auto max-w-2xl">
    <.flash_group flash={@flash} />
    <%= @inner_content %>
  </div>
</main>

The hook

Hooks.MainHook = {
    mounted() {
        let token = localStorage.getItem("uuid")
        if(token == null) {
            // here you should push an event to the server and fetch a
            // tamper proof token and save it to localStorage and
            // all other security considerations
            token = "1234"
        }
        this.pushEvent("subscribe_to_channel", {uuid: token})
    }
}

The module attaching the server hooks

defmodule ExampleWeb.MultipleTabs do
  import Phoenix.LiveView

  def on_mount(:default, _params, _session, socket) do
    {:cont,
     socket
     |> attach_hook(
       "subscribe_to_channel",
       :handle_event,
       &subscribe_to_channel/3
     )
     |> attach_hook(:handle_reload, :handle_info, &reload_page/2)}
  end

  defp reload_page(_msg, socket) do
    # Here you'll need to figure out a way to get the url
    # to redirect to the correct place
    {:cont, socket |> redirect(to: "/")}
  end

  defp subscribe_to_channel("subscribe_to_channel", %{"uuid" => uuid}, socket) do
    # Here you should validate the token before subscribing
    Phoenix.PubSub.subscribe(Example.PubSub, "reload##{uuid}")
    {:cont, socket}
  end
end

And then just put it in a live session in the router

    live_session :default, on_mount: ExampleWeb.MultipleTabs do
      live "/", HomeLive
    end

Then you can call Phoenix.PubSub.broadcast(Example.PubSub, "reload#1234", []) from anywhere and all pages will redirect to /.

Security considerations aside it is pretty simple.

shamanime

shamanime

You’ve implemented phx.gen.auth so you’re probably aware of the LiveView disconnect when the user logs out. That forces the tabs to reload.

If you come up with a way to identify all the tabs which belongs to the same guest user (ip?, browser fingerprint?, setting an identifier in the session at first page load and using it afterwards [like a fake user id]?) and set the live_socket_id accordingly, you can also disconnect all the “guest” LiveViews to force a page reload that will fetch the newly signed in user.

thomas.fortes

thomas.fortes

My naive first approach would be something like:

  1. At any new tab check if there’s a token (preferably tamper proof) in local storage and send it to the server, if not, ask for one from the server with an unique identifiable payload (maybe an UUID) and save it in the local storage, in this case only the first page load will actually create a token.

  2. Using on_mount use attach_hook/4 to handle the pushed event with the uuid to subscribe to a “guest#{uuid}” channel and the incoming handle_info/2 messages from pubsub.

  3. When the user successfully log in you can broadcast a message to the guest#{uuid} channel that will be handled by the handle_info/2 callback of all liveviews subscribed to that channel.

Security considerations aside (short lived tokens, single use, yadda, yadda, yadda) I think someone could build it in a couple hours.

A bit more javascript than I like to write though…

Where Next?

Popular in Questions Top

RisingFromAshes
I’ve read in another post that it may be possible with a router helper - but I couldn’t find an appropriate one, and tbh, I’m still just ...
New
dotdotdotPaul
Okay, I’m having a heck of a time trying to figure out how to best handle the validation of belongs_to associations in Ecto. I’m sure I’...
New
vertexbuffer
Hello, can anybody help here..? I have a list of players and I what to delete an element, but every for loop the list is reverting to ori...
New
jaysoifer
Is there a way to rollback a specific migration and only that one (“skipping” all the other ones)? Would mix ecto.rollback -v 200809061...
New
sergio
In Ruby, I can go: User.find_by(email: "foobar@email.com").update(email: "hello@email.com") How can I do something similar in Elixir? ...
New
ashish173
I am using Ecto timestamps with postgres, I can see the timestamps() use the :naive_dateime but for my use case I wanted to store the ti...
New
Lily
In templates/appointment/index.html.eex: &lt;%= for appointment &lt;- @appointments do %&gt; &lt;tr&gt; &lt;td&gt;&lt;%= appoi...
New
earth10
Hi, I’m just starting to build a side-project with Elixir and Phoenix and doing some basic test with Elixir alone. What strikes me is th...
New
siddhant3030
Hi, I have to write a raw query for one of my project. But till now I have used ecto queries and don’t have much experience writing raw ...
New
itssasanka
Hi all, Trying to get some more clarity over utc_datetime and naive_datetime for Ecto: The documentation above suggests that while ...
New

Other popular topics Top

vegabook
I’m brand new to Phoenix and I have stripped one of the demo applications to the bone. I just want to get an svg up on the screen. Here i...
New
johnnyicon
Hi all, I’ve just started learning Elixir and Phoenix Framework, so please pardon my n00bness at this stage. I’m trying to use Postgres...
New
hariharasudhan94
I would like to know what is the best IDE for elixir development?
New
lessless
I believe there are people here who are dealing with CSV files import on the daily basis, and since Excel is a really popular tool there ...
New
shahryarjb
Hello, I have map which I want to convert it to string like this: the map: %{last_name: "tavakkoli", name: "shahryar"} the string I ne...
New
stefanluptak
Hello everybody, usually, I use a 29" ultra-wide monitor for VSCode which can easily accomodate explorer (files panel) + file with code ...
New
klo
Got a question about when to concat vs. prepending items to list then reversing to achieve appending. So i know lists boil down to [1 | ...
New
saif
Hello everyone, Long time lurker first time poster here. I’ve recently begun working on Elixir full-time again! :raised_hands: It’s been...
New
SoCreat
i’m a new one to elixir which editor can i use vs code? or atom? Thanks! :smiley:
New
dblack
I’ve got an issue with an app and I’ve no idea of how to troubleshoot it. I’m hoping someone here might have seen something similar. I p...
New

We're in Beta

About us Mission Statement