Curated library of 38 atomic skills, 7 personas, and 1 orchestrator for Elixir and Phoenix development. Organized by category: fundamentals, phoenix, database, testing, auth, infrastructure, quality, security, integrations, tooling, frameworks, personas, and orchestration. Covers core Elixir patterns, Phoenix LiveView, Ecto, OTP, Oban, testing, security, deployment, real-time, and modern tooling (Req, Swoosh, Cachex, Broadway, Ash).
91
91%
Does it follow best practices?
Impact
91%
1.37xAverage score across 56 eval scenarios
Advisory
Suggest reviewing before use
Use this skill before writing ANY on_mount hook or LiveView auth code.
Requires
phoenix-scopesfor Scope struct setup. Seephoenix-authorization-patternsfor access control after authentication, andphoenix-liveview-essentialsbefore writing any LiveView module.
on_mount callbacks — never check auth in mount/3 directly:halt must redirect with a flash message — never silently drop the connectionon_mount hooks once, reference via live_session in router — never duplicate auth logic across LiveView modulesimport Phoenix.Controller, except: [redirect: 2, put_flash: 3] — so LiveView's redirect/2 and put_flash/3 take precedenceassigns[:current_scope] in templates that can render unauthenticated — never @current_scope directly (raises KeyError)@impl true to every LiveView callback — including a mount/3 that reads socket.assigns.current_scope.useron_mount hooks in UserAuth with import conflict resolution; verify with mix compilelive_session blocks to the router referencing those hooks; verify with mix phx.routesmix test test/my_app_web/live/ and assert redirect tuples match expected paths; if tests fail, check session config and verify Accounts.get_user_by_session_token/1 returns the correct userdefmodule MyAppWeb.UserAuth do
use MyAppWeb, :verified_routes
import Phoenix.LiveView
# Exclude Phoenix.Controller's redirect/2 and put_flash/3 so LiveView's versions take precedence
import Phoenix.Controller, except: [redirect: 2, put_flash: 3]
def on_mount(:require_authenticated_user, _params, session, socket) do
socket = mount_current_scope(socket, session)
if socket.assigns.current_scope && socket.assigns.current_scope.user do
{:cont, socket}
else
socket =
socket
|> put_flash(:error, "You must log in to access this page.")
|> redirect(to: ~p"/users/log_in")
{:halt, socket}
end
end
def on_mount(:redirect_if_authenticated, _params, session, socket) do
socket = mount_current_scope(socket, session)
if socket.assigns.current_scope && socket.assigns.current_scope.user do
{:halt, redirect(socket, to: ~p"/")}
else
{:cont, socket}
end
end
def on_mount(:mount_current_scope, _params, session, socket) do
{:cont, mount_current_scope(socket, session)}
end
defp mount_current_scope(socket, session) do
Phoenix.Component.assign_new(socket, :current_scope, fn ->
if user = find_user_from_session(session) do
%Scope{user: user}
end
end)
end
defp find_user_from_session(%{"user_token" => token}) do
Accounts.get_user_by_session_token(token)
end
defp find_user_from_session(_session), do: nil
endSee assets/on_mount_template.ex for copy-paste on_mount templates (Phoenix 1.7 current_user, Phoenix 1.8+ Scope, and role-based variants).
defmodule MyAppWeb.Router do
use MyAppWeb, :router
live_session :mount_current_scope,
on_mount: [{MyAppWeb.UserAuth, :mount_current_scope}] do
scope "/", MyAppWeb do
pipe_through :browser
live "/", HomeLive.Index
end
end
live_session :require_authenticated_user,
on_mount: [{MyAppWeb.UserAuth, :require_authenticated_user}] do
scope "/", MyAppWeb do
pipe_through [:browser, :require_authenticated_user]
live "/dashboard", DashboardLive.Index
live "/settings", SettingsLive.Index
end
end
live_session :redirect_if_authenticated,
on_mount: [{MyAppWeb.UserAuth, :redirect_if_authenticated}] do
scope "/", MyAppWeb do
pipe_through [:browser, :redirect_if_user]
live "/users/register", UserRegistrationLive
live "/users/log_in", UserLoginLive
end
end
end@impl true
def mount(_params, _session, socket) do
user = socket.assigns.current_scope.user
{:ok, assign(socket, :posts, Posts.list_posts(user))}
end<%# Use assigns[:current_scope] (bracket access), not @current_scope — avoids KeyError on unauthenticated sockets %>
<%= if assigns[:current_scope] && @current_scope.user do %>
<p>Welcome, <%= @current_scope.user.email %></p>
<% end %>describe "require_authenticated_user" do
test "redirects if not logged in", %{conn: conn} do
assert {:error, {:redirect, %{to: "/users/log_in"}}} =
live(conn, ~p"/dashboard")
end
test "renders page when authenticated", %{conn: conn} do
user = user_fixture()
conn = log_in_user(conn, user)
{:ok, _lv, html} = live(conn, ~p"/dashboard")
assert html =~ "Dashboard"
end
end
describe "redirect_if_authenticated" do
test "redirects if already logged in", %{conn: conn} do
user = user_fixture()
conn = log_in_user(conn, user)
assert {:error, {:redirect, %{to: "/"}}} =
live(conn, ~p"/users/log_in")
end
end| ❌ Don't | ✅ Do |
|---|---|
Check auth inside mount/3 with a manual token lookup | Use an on_mount hook wired through live_session |
Return {:halt, socket} with no redirect or flash | redirect(socket, to: ~p"/users/log_in") plus a put_flash/3 |
import Phoenix.Controller unfiltered (shadows LiveView helpers) | import Phoenix.Controller, except: [redirect: 2, put_flash: 3] |
Reference @current_scope in templates that render for guests | Use assigns[:current_scope] && @current_scope.user (bracket access) |
| Repeat the auth check in every LiveView module | Group routes under a live_session with the shared hook |
Omit @impl true on LiveView callbacks | Add @impl true above mount/3, handle_event/3, etc. |
| Predecessor | This Skill | Successor |
|---|---|---|
| phoenix-scopes | phoenix-liveview-auth | phoenix-authorization-patterns |
| phoenix-liveview-essentials | phoenix-liveview-auth | phoenix-auth-customization |
Companion skills: phoenix-scopes, phoenix-authorization-patterns, testing-essentials
.tessl-plugin
evals
scenario-1
scenario-2
scenario-3
scenario-4
scenario-5
scenario-6
scenario-7
scenario-8
scenario-9
scenario-10
scenario-11
scenario-12
scenario-13
scenario-14
scenario-15
scenario-16
scenario-17
scenario-18
scenario-19
scenario-20
scenario-21
scenario-22
scenario-23
scenario-24
scenario-25
scenario-26
scenario-27
scenario-28
scenario-29
scenario-30
scenario-31
scenario-32
scenario-33
scenario-34
scenario-35
scenario-36
scenario-37
scenario-38
scenario-39
scenario-40
scenario-41
scenario-42
scenario-43
scenario-44
scenario-45
scenario-46
scenario-47
scenario-48
scenario-49
scenario-50
scenario-51
scenario-52
scenario-53
scenario-54
scenario-55
scenario-56
skills
frameworks
ash-framework
infrastructure
orchestration
elixir-skill-router
personas
phoenix
quality
security
security-essentials
tooling
mix-tasks-generators