A built in live session wrapper is provided that will set the user assigns for you. To use it, wrap your live routes like so:
ash_authentication_live_session :session_name do
live "/route", ProjectLive.Index, :index
endThere are two problems with the above, however.
- If there is no user present, it will not set
current_user: nil. - You may want a way to require that a user is present for some routes, and not for others.
To accomplish this, we use standard Phoenix on_mount hooks. Lets define a hook that gives us three potential behaviors, one for optionally having a user signed in, one for requiring a signed in user, and one for requiring that there is no signed in user.
# lib/my_app_web/live_user_auth.ex
defmodule MyAppWeb.LiveUserAuth do
@moduledoc """
Helpers for authenticating users in LiveViews.
"""
import Phoenix.Component
use MyAppWeb, :verified_routes
def on_mount(:live_user_optional, _params, _session, socket) do
if socket.assigns[:current_user] do
{:cont, socket}
else
{:cont, assign(socket, :current_user, nil)}
end
end
def on_mount(:live_user_required, _params, _session, socket) do
if socket.assigns[:current_user] do
{:cont, socket}
else
{:halt, Phoenix.LiveView.redirect(socket, to: ~p"/sign-in")}
end
end
def on_mount(:live_no_user, _params, _session, socket) do
if socket.assigns[:current_user] do
{:halt, Phoenix.LiveView.redirect(socket, to: ~p"/")}
else
{:cont, assign(socket, :current_user, nil)}
end
end
endAnd we can use this as follows:
# lib/my_app_web/router.ex
# ...
scope "/", MyAppWeb do
# ...
ash_authentication_live_session :authentication_required,
on_mount: {MyAppWeb.LiveUserAuth, :live_user_required} do
live "/protected_route", ProjectLive.Index, :index
end
ash_authentication_live_session :authentication_optional,
on_mount: {MyAppWeb.LiveUserAuth, :live_user_optional} do
live "/", ProjectLive.Index, :index
end
end
# ...If you want to allow access to a live_view based on users role or some other condition:
def on_mount({:required_role, role}, _params, _session, socket) do
if socket.assigns[:current_user] && socket.assigns[:current_user].role == role do
{:cont, socket}
else
{:halt, Phoenix.LiveView.redirect(socket, to: ~p"/")}
end
endYou can also match multiple roles:
def on_mount({:required_roles, roles}, _params, _session, socket) do
if socket.assigns[:current_user] && Enum.any?(roles, &(socket.assigns[:current_user].role == &1)) do
{:cont, socket}
else
{:halt, Phoenix.LiveView.redirect(socket, to: ~p"/")}
end
endUse it in a on_mount call in a LiveView:
on_mount {MyAppWeb.LiveUserAuth, {:required_role: :admin}}on_mount {MyAppWeb.LiveUserAuth, {:required_roles: [:admin, :staff]}}You can also use this to prevent users from visiting the auto generated sign_in route:
sign_in_route(on_mount: [{MyAppWeb.LiveUserAuth, :live_no_user}])When configured, AshAuthentication.Phoenix can automatically disconnect all LiveView sessions when a user signs out. This ensures that tabs left open won't continue to function after the user has signed out elsewhere.
- On sign-in,
set_live_socket_id/2stores a session identifier based on the token's JTI - When the user signs out, the token is revoked
- The
TokenRevocationNotifierbroadcasts a "disconnect" message to all configured endpoints - Phoenix disconnects any LiveView sockets with that session ID
This feature is automatically configured when you run the installer. If you're upgrading an existing installation, you'll need to:
- Update your auth controller to call
set_live_socket_id/2:
def success(conn, _activity, user, token) do
conn
|> store_in_session(user)
|> set_live_socket_id(token) # Add this line
|> redirect(to: ~p"/")
end- Configure your Token resource:
defmodule MyApp.Accounts.Token do
use Ash.Resource,
extensions: [AshAuthentication.TokenResource],
simple_notifiers: [AshAuthentication.Phoenix.TokenRevocationNotifier]
token do
endpoints [MyAppWeb.Endpoint]
live_socket_id_template &("users_sessions:#{&1["jti"]}")
end
end