mirror of
https://github.com/team-alembic/ash_authentication_phoenix.git
synced 2024-09-19 21:13:52 +12:00
docs: add liveview specific guide
This commit is contained in:
parent
5c0263e205
commit
d9a3503430
3 changed files with 91 additions and 6 deletions
|
@ -619,3 +619,7 @@ end
|
|||
```
|
||||
|
||||
Your new reset password functionality is active. Visit [`localhost:4000/sign-in`](http://localhost:4000/sign-in) with your browser and click on the `Forgot your password?` link to trigger the reset password workflow.
|
||||
|
||||
# Next up
|
||||
|
||||
- [Use AshAuthentication with LiveView](/documentation/tutorials/use-ash-authentication-with-liveview.md)
|
|
@ -0,0 +1,73 @@
|
|||
# Setting up your routes
|
||||
|
||||
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:
|
||||
|
||||
```elixir
|
||||
ash_authentication_live_session :session_name do
|
||||
live "/route", ProjectLive.Index, :index
|
||||
end
|
||||
```
|
||||
|
||||
# LiveUserAuth
|
||||
|
||||
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.
|
||||
|
||||
To accomplish this, we use standard Phoenix [`on_mount` hooks](https://hexdocs.pm/phoenix_live_view/Phoenix.LiveView.html#on_mount/1-examples). 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.
|
||||
|
||||
```elixir
|
||||
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:
|
||||
|
||||
```elixir
|
||||
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
|
||||
```
|
||||
|
||||
You can also use this to prevent users from visiting the auto generated `sign_in` route:
|
||||
|
||||
```elixir
|
||||
sign_in_route(on_mount: [{MyAppWeb.LiveUserAuth, :live_no_user}])
|
||||
```
|
|
@ -30,7 +30,8 @@ defmodule AshAuthentication.Phoenix.LiveSession do
|
|||
* `:otp_app` - Set the otp app in which to search for authenticated resources.
|
||||
|
||||
All other options are passed through to `live_session`, but with session and on_mount hooks
|
||||
added to set assigns for authenticated resources.
|
||||
added to set assigns for authenticated resources. Unlike `live_session`, this supports
|
||||
multiple MFAs provided for the `session` option. The produced sessions will be merged.
|
||||
"""
|
||||
@spec ash_authentication_live_session(atom, opts :: Keyword.t()) :: Macro.t()
|
||||
defmacro ash_authentication_live_session(session_name \\ :ash_authentication, opts \\ [],
|
||||
|
@ -41,12 +42,12 @@ defmodule AshAuthentication.Phoenix.LiveSession do
|
|||
|
||||
opts = unquote(opts)
|
||||
|
||||
session = {LiveSession, :generate_session, [opts[:otp_app]]}
|
||||
session = {LiveSession, :generate_session, [opts[:otp_app], List.wrap(opts[:session])]}
|
||||
|
||||
opts =
|
||||
opts
|
||||
|> Keyword.update(:on_mount, on_mount, &(on_mount ++ List.wrap(&1)))
|
||||
|> Keyword.update(:session, session, &[session | List.wrap(&1)])
|
||||
|> Keyword.put(:session, session)
|
||||
|
||||
{otp_app, opts} = Keyword.pop(opts, :otp_app)
|
||||
|
||||
|
@ -114,14 +115,21 @@ defmodule AshAuthentication.Phoenix.LiveSession do
|
|||
Supplements the session with any `current_X` assigns which are authenticated
|
||||
resource records from the conn.
|
||||
"""
|
||||
@spec generate_session(Plug.Conn.t(), atom | [atom]) :: %{required(String.t()) => String.t()}
|
||||
def generate_session(conn, otp_app \\ nil) do
|
||||
@spec generate_session(Plug.Conn.t(), atom | [atom], additional_hooks :: [mfa]) :: %{
|
||||
required(String.t()) => String.t()
|
||||
}
|
||||
def generate_session(conn, otp_app \\ nil, additional_hooks \\ []) do
|
||||
otp_app = otp_app || conn.assigns[:otp_app] || conn.private.phoenix_endpoint.config(:otp_app)
|
||||
|
||||
acc =
|
||||
Enum.reduce(additional_hooks, %{}, fn {m, f, a}, acc ->
|
||||
Map.merge(acc, apply(m, f, [conn | a]) || %{})
|
||||
end)
|
||||
|
||||
otp_app
|
||||
|> AshAuthentication.authenticated_resources()
|
||||
|> Stream.map(&{to_string(Info.authentication_subject_name!(&1)), &1})
|
||||
|> Enum.reduce(%{}, fn {subject_name, resource}, session ->
|
||||
|> Enum.reduce(acc, fn {subject_name, resource}, session ->
|
||||
case Map.fetch(
|
||||
conn.assigns,
|
||||
String.to_existing_atom("current_#{subject_name}")
|
||||
|
|
Loading…
Reference in a new issue