I have an app where users have different permissions. Those permissions are used to determine whether a user can see a page. Each route has one or more permissions associated with it, for dead views it’s easy enough to define a plug that checks for the authorisation and use the private conn fields to pass along the permission info, eg:
# router.ex
scope "/", MyWeb do
pipe_through([:browser, :authorize])
...
get("/some-page", SomePage :index, private: %{required_permissions: [:admin]})
...
end
# user_auth.ex
def authorize(conn, _opts) do
required_permissions = Map.get(conn.private, :required_permissions)
user = conn.assigns[:current_user]
if user && user.confirmed_at && required_permissions do
if Users.has_all_permissions?(user, required_permissions) do
conn
else
conn
|> Phoenix.Controller.put_flash(:error, "Unauthorized.")
|> Phoenix.Controller.redirect(to: ~p"/")
|> Plug.Conn.halt()
end
else
...
end
end
For liveview though we want to check the permissions on mount. Mount does not get conn it gets params, session and socket, so even though you can do this:
# router.ex
live("the-page", Pages.ThePage, :new, private: %{required_permissions: [:admin]})
The required_permissions are not available to you. You can instead do this:
live_session :admin_user,
session: %{"required_permissions" => [:admin]},
on_mount: [{UserAuth, :ensure_authenticated}] do
live("/the-page", ThePage, :new)
end
and you will see required_permissions in session. But this means you have to make a new live_session for each unqiue set of permissions. Eg if I have one route that requires :view_users and one route that requires :admin they now can’t be in the same live_session, which means you miss out on the benefit of live_redirects.
What are some strategies you’ve tried to avoid this?
The only other thing I’ve tried is inverting the control by specifying the route in a live_view and using that in an on_mount
def live_view(route) do
quote do
...
unquote(html_helpers())
on_mount({AuthorizeRoute, unquote(route)})
end
end
defmacro __using__(:live_view) do
raise_message()
end
defmacro __using__(which) when is_atom(which) do
apply(__MODULE__, which, [])
end
defp raise_message() do
raise """
When defining a live view you should provide the type and route options like so:
use MyAppWeb, type: :live_view, route: :the_route
The route should have a matching function head in AuthorizeRoute.on_mount/4
so that the live_view can be authorized.
"""
end
# in a live view:
defmodule MyAppWeb.Pages.ThePage do
use MyAppWeb, type: :live_view, route: :the_page
...
end
# authorixe_route.ex
defmodule AuthorizeRoute do
@permissions %{
the_page [:admin],
some_other_route: [:view_users]
}
def on_mount(route, params, session, socket) do
if permissions = Map.get(@permissions, route, nil) do
user = socket.assigns.current_user
if Users.has_all_permissions?(user, permission) do
...
else
....
end
else
# Could flash
{:halt, Phoenix.LiveView.redirect(socket, to: signed_in_path(socket))}
end
end
end
But that feels kinda nasty.




















