Skip to content

Latest commit

 

History

History
161 lines (128 loc) · 4.56 KB

File metadata and controls

161 lines (128 loc) · 4.56 KB

Setting up your routes for LiveView

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
end

There are two problems with the above, however.

  1. If there is no user present, it will not set current_user: nil.
  2. You may want a way to require that a user is present for some routes, and not for others.

Authentication helper

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
end

And 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
end

You 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
end

Use 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}])

Automatic Session Disconnection on Sign Out

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.

How It Works

  1. On sign-in, set_live_socket_id/2 stores a session identifier based on the token's JTI
  2. When the user signs out, the token is revoked
  3. The TokenRevocationNotifier broadcasts a "disconnect" message to all configured endpoints
  4. Phoenix disconnects any LiveView sockets with that session ID

Configuration

This feature is automatically configured when you run the installer. If you're upgrading an existing installation, you'll need to:

  1. 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
  1. 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