defmodule PetalPro.Accounts.User do
...
schema "users" do
field :name, :string
field :email, :string
field :password, :string, virtual: true, redact: true
field :hashed_password, :string, redact: true
field :confirmed_at, :naive_datetime
field :is_admin, :boolean, default: false
field :avatar, :string
field :last_signed_in_ip, :string
field :last_signed_in_datetime, :utc_datetime
field :is_subscribed_to_marketing_notifications, :boolean, default: true
field :is_suspended, :boolean, default: false
field :is_deleted, :boolean, default: false
field :is_onboarded, :boolean, default: false
timestamps()
end
...
end
Users have some extra fields not included by phx.gen.auth:
Field
Type
Description
name
:string
A users full name
avatar
:string
A URL to the users avatar image
last_signed_in_ip
:string
The IP address of the last login by this user.
is_subscribed_to_marketing_notifications
:boolean
Track whether a user wants to receive marketing emails or not.
is_admin
:boolean
Admins get access to a special dashboard where they can modify users, see logs, etc.
is_suspended
:boolean
An admin can suspend a user, preventing them from logging in.
is_deleted
:boolean
Allows for soft deletion of users
is_onboarded
:boolean
Track whether or not a user has seen an onboarding screen after registering.
Authentication
Setting and accessing the current user
Controller actions
For controller actions we use the plug provided by mix phx.gen.auth to set conn.assigns.current_user .
user_auth.ex
defmodule PetalProWeb.UserAuth do
...
def fetch_current_user(conn, _opts) do
{user_token, conn} = ensure_user_token(conn)
user = user_token && Accounts.get_user_by_session_token(user_token)
assign(conn, :current_user, user)
end
...
end
You can see the :fetch_current_user plug used in the :browser pipeline in the router.
router.ex
defmodule PetalProWeb.Router do
...
pipeline :browser do
plug :accepts, ["html"]
plug :fetch_session
plug :fetch_live_flash
plug :put_root_layout, {PetalProWeb.LayoutView, :root}
plug :protect_from_forgery
plug :put_secure_browser_headers
plug :fetch_current_user
plug PetalProWeb.SetLocalePlug, gettext: PetalProWeb.Gettext
plug :set_color_scheme
end
...
end
If you want to enforce the user then you can use the :require_authenticated_user plug.
router.ex
scope "/", PetalProWeb do
pipe_through [:browser, :require_authenticated_user]
# Routes that require a logged in user go here
end
Live views
We can't rely on our plugs in live views, since live views connect over web sockets and avoid the traditional request/response lifecycle. However, a live view will have access to the session, which contains the user token set upon login. Hence, in the live view mount we can use the token to find the user_token set in our database for that users session, and from there obtain the logged in user.
user_on_mount_hooks.ex
defmodule PetalProWeb.UserOnMountHooks do
...
def on_mount(:require_authenticated_user, _params, session, socket) do
socket = maybe_assign_user(socket, session)
if socket.assigns.current_user do
{:cont, socket}
else
{:halt, redirect(socket, to: Routes.user_session_path(socket, :new))}
end
end
defp maybe_assign_user(socket, session) do
assign_new(socket, :current_user, fn ->
get_user(session["user_token"])
end)
end
...
end
router.ex
defmodule PetalProWeb.Router do
...
scope "/", PetalProWeb do
pipe_through [:browser, :require_authenticated_user]
live_session :protected, on_mount: {PetalProWeb.UserOnMountHooks, :require_authenticated_user} do
# Live routes that require a logged in user go here
end
end
...
end
We used (email/password) and modified the templates to use Tailwind and Petal Components.
Instead of doing this on every live view mount function, we can extract this out into an function and then apply it in the router, like a mini pipeline.