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.
|
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.
|
* `: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
|
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()
|
@spec ash_authentication_live_session(atom, opts :: Keyword.t()) :: Macro.t()
|
||||||
defmacro ash_authentication_live_session(session_name \\ :ash_authentication, opts \\ [],
|
defmacro ash_authentication_live_session(session_name \\ :ash_authentication, opts \\ [],
|
||||||
|
@ -41,12 +42,12 @@ defmodule AshAuthentication.Phoenix.LiveSession do
|
||||||
|
|
||||||
opts = unquote(opts)
|
opts = unquote(opts)
|
||||||
|
|
||||||
session = {LiveSession, :generate_session, [opts[:otp_app]]}
|
session = {LiveSession, :generate_session, [opts[:otp_app], List.wrap(opts[:session])]}
|
||||||
|
|
||||||
opts =
|
opts =
|
||||||
opts
|
opts
|
||||||
|> Keyword.update(:on_mount, on_mount, &(on_mount ++ List.wrap(&1)))
|
|> 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)
|
{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
|
Supplements the session with any `current_X` assigns which are authenticated
|
||||||
resource records from the conn.
|
resource records from the conn.
|
||||||
"""
|
"""
|
||||||
@spec generate_session(Plug.Conn.t(), atom | [atom]) :: %{required(String.t()) => String.t()}
|
@spec generate_session(Plug.Conn.t(), atom | [atom], additional_hooks :: [mfa]) :: %{
|
||||||
def generate_session(conn, otp_app \\ nil) do
|
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)
|
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
|
otp_app
|
||||||
|> AshAuthentication.authenticated_resources()
|
|> AshAuthentication.authenticated_resources()
|
||||||
|> Stream.map(&{to_string(Info.authentication_subject_name!(&1)), &1})
|
|> 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(
|
case Map.fetch(
|
||||||
conn.assigns,
|
conn.assigns,
|
||||||
String.to_existing_atom("current_#{subject_name}")
|
String.to_existing_atom("current_#{subject_name}")
|
||||||
|
|
Loading…
Reference in a new issue