I’m trying to get a centralized authentication/authorization system working by using the LiveView on_mount hook, not the handle_params.
This app has pages with different levels of authorization:
- public: can be accessed by anyone, no need to be authenticated
- require authentication: need authenticated users
- require a specific user role: only users with specific roles can access (i.e :admin)
The users can live navigate through those pages, so an unauthenticated user may be able to click a button that navigates to a live view where authentication is required. So the objectives are pretty simple:
- If an unauthenticated user is trying to (live) navigate to a LiveView that requires authentication, we redirect to login.
- after logging in the user is redirected back to the page he wanted to access in the first place (important bit).
- if the user is authenticated but not authorized, we just redirect to home and show a flash message.
I have a module that handles the app authorization based on the URI:
MyApp.authorized(user, uri)
My router:
live_session :default,
on_mount: [
{MyAppWeb.UserAuth, :mount_current_user},
{MyAppWeb.UserAuth, :ensure_authorized}
] do
...
How do I get or build the URI in the on_mount stage? I saw several forum posts and GitHub issues talking about using the handle_params instead. However, that creates several issues in my existing live views because the LiveViews on_mount are executing before authentication/authorization is done. Ideally, I would like to only run the LiveView code once authorization is done. I saw we use to have a few routing helpers (live_path) that would allow me to create the URI based on the live view module (socket.view) and params but that doesn’t exist anymore.
def on_mount(:ensure_authorized, _params, _session, socket) do
uri = "" # <- how to get or build the URI at this stage???
%{assigns: %{current_user: current_user}} = socket
case Authorization.authorized(current_user, uri) do
true ->
socket
|> Phoenix.Component.assign(:live_url, uri)
|> cont()
{:error, :not_authorized} ->
socket
|> Phoenix.LiveView.put_flash(:error, "Not Authorized")
|> Phoenix.LiveView.redirect(to: ~p"/")
|> halt()
{:error, :requires_login} ->
socket
|> Phoenix.LiveView.put_flash(:error, "You must log in to access this page.")
|> Phoenix.LiveView.redirect(to: ~p"/users/log_in?#{%{return_to: uri}}")
|> halt()
end
end