How secure live_views with on_mount

Hi all,

I currently build an app with authentication and authorization.
As foundation I use the latest mix phx.gen.auth.
And my app contains (except of some authentication routes) almost only live_views.

I have completely read the live_view docs and think I understood it also. Especially about security model

I have a question about the past paragraph on this page:

live_session can be used to draw boundaries between groups of LiveViews. While you could use live_session to draw lines between different authorization rules, doing so would lead to frequent page reloads. For this reason, we typically use live_session to enforce different authenticationrequirements or whenever you need to change root layouts

So that’s clear and I recognized that already, that a page-reload is triggered, when navigating between live_session.

From the said, I thought I would do:

  • live_session blocks for authentication
  • and on_mount for checking authorization.

But how to do that best?
I thought I just use the on_mount hook in my live_view like so…

defmodule MyAppWeb.MySecretPageLive do
  use PortalWeb, :live_view

  on_mount MyAppWeb.UserAuth, :any

… but okay, that’s not how you use it…

The on_mount should look something like this, where I can conditionally add a permission, which will be checked if the user has the permission…

  def on_mount(permission, _params, session, socket) do
    socket = mount_current_user(session, socket)

    if permission in socket.assigns.current_user.acl do
      {:cont, socket}
    else
      {:halt, redirect(socket, to: "/login")}
    end
  end

So any hints what’s best practice?
Or should I just also wrap authorization into live_sessions ? But as the docs say, that is probably not the best way for user-experience?

Okay… and then you once again figured out: RTFM !!!
Its all written here: Phoenix.LiveView — Phoenix LiveView v0.18.18

So my problem is solved and I just can once again empathize: Elixir at itself is just awesome… but have you read the docs ??? Awesome :smiley:

1 Like

I read your question, but didn’t really understand what you were asking :laughing:

But just to confirm: on_mount is a good mechanism to make reusable authentication/authorization hooks available, for multiple LiveViews. The generated code by phx.gen.auth is a good example on how to accomplish that. As you’ve now undoubtedly have found out, you can use the hooks on two levels: on live_sessions, or on individual LVs.

Good luck securing thise LVs!

Also remember to secure your socket against from being abused from the dev tools in the browser. As an attacker I can hit F12 and then just manually attack your liveview.

Another thing you need to be aware of is the attacker can create a for loop in dev tools to overload your liveview with thousands/millions of requests as fast as the browser can.

Yeah, sorry. That was not relay clear maybe.
So basically my issue was, that I did not know how to use a certain on_mount hook in a live_view by passing an argument.

The docs gave the answer, that I can just use a tuple like this:

on_mount {MyAppWeb.UserAuth, :admin}

That’s a topic I am interested in.
Do you have further information or resources how this vector is working? The manual attack of the socket? Triggering requests on a server is probably a general vector and not specific to elixir Phoenix nor live_views, right? Basically a dos attack, that should be handled by upper-layer mechanisms like load-balancers and ddos preventing firewalls.

I think you can always mess with the client code but from my understanding if you follow the docs and secure your html requests, the socket connections on mount and all handle-* things…
one should be fine?
Indeed a bit work, but what isn’t… :slightly_smiling_face:

1 Like

In some situations, it may be necessary to pass additional options to the hook callback, similar to using a plug with an optional opts argument that defaults to [].

Although I initially thought that something like on_mount {MyAppWeb.UserAuth, :admin} was not sufficient for my use case, I later realized that the solution was already within the formula.

It just wasn’t very explicit or obvious to me, but I just realised that the second item of the tuple can be any type of data, such as another tuple, a list, or a map. Therefore I was able to define a more suitable hook for my use case.

So one can also define a hook like this:

on_mount {MyApp.RoleAuth, {:ensure_role_in, [:superadmin, :admin, :moderator]}}

That will match an on_mount clause in a RoleAuth module that looks like below:

def on_mount({:ensure_role_in, roles}, _params, _session, socket) when is_list(roles) do
  user = socket.assigns.current_user

  if user.role in roles do
    {:cont, socket}
  else
    socket =
      socket
      |> Phoenix.LiveView.put_flash(:error, "Access denied!")
      |> Phoenix.LiveView.redirect(to: ~p"/")

    {:halt, socket}
  end
end
2 Likes

That’s super cool. Thanks :smiley:

1 Like