Phoenix Framework and LiveView integration for Permit authorization library.
Permit.Phoenix
allows for consistent authorization of actions throughout the entire codebase of a Phoenix application, both in Plug-based controllers and in LiveView.
Permit
provides the permission definition syntaxPermit.Ecto
is optional, but - if present - it constructs queries to look up accessible records from a database, based on defined permissionsPermit.Phoenix
plugs into controllers and live views in order to automatically preload records and check authorization permissions to perform actions.
The package can be installed by adding permit_phoenix
to your list of dependencies in mix.exs
:
def deps do
[
{:permit_phoenix, "~> 0.1.0"},
# :permit_ecto can be omitted if Ecto is not used
{:permit_ecto, "~> 0.1.1"}
]
end
All options of Permit.Phoenix.Controller
can be provided as option keywords with use Permit.Phoenix.Controller
or as callback implementations. For example, defining a handle_unauthorized: fn action, conn -> ... end
option is equivalent to:
@impl true
def handle_unauthorized(action, conn), do: ...
In practice, it depends on use case:
- when providing options for different actions, etc., consider using callback implementations
- if you want to provide values as literals instead of functions, consider using option keywords
- for global settings throughout controllers using
use MyAppWeb, :controller
, set globals as keywords, and override in specific controllers using callback implementations.
Whenever resolution_context
is referred to, it is typified by Permit.Types.resolution_context
.
defmodule MyAppWeb.ArticleController do
use Permit.Phoenix.Controller,
# Mandatory options:
authorization_module: MyApp.Authorization,
resource_module: MyApp.Article,
# Additional available options:
fallback_path: fn action, conn -> ... end,
handle_unauthorized: fn action, conn -> ... end,
fetch_subject: fn conn -> ... end,
preload_actions: [:action1, :action2, ...],
except: [:action3, :action4, ...],
id_param_name: fn action, conn -> ... end,
id_struct_field_name: fn action, conn -> ... end,
# Non-Ecto only:
loader: fn resolution_context -> ... end,
# Ecto only:
base_query: fn resolution_context -> ... end,
finalize_query: fn query, resolution_context -> ... end
def show(conn, params) do
# If there is a MyApp.Article with ID == params[:id] that
# matches the current user's permissions, it will be
# available as the @loaded_resource assign.
#
# Otherwise, handle_unauthorized/2 is called, defaulting to
# redirecting to `/`.
end
def index(conn, params) do
# If the :index action is authorized for the user, the
# @loaded_resources assign will contain all records accessible
# by the current user per the app's permissions configuration.
#
# Pagination and other concerns can be configured with
# the base_query/1 callback.
#
# Otherwise, handle_unauthorized/2 is called, defaulting to
# redirecting to `/`.
end
end
defmodule MyAppWeb do
def controller do
quote do
# ...
use Permit.Phoenix.Controller,
authorization_module: MyApp.Authorization,
# global options go here
end
end
end
defmodule MyAppWeb.ArticleController do
use MyAppWeb, :controller
@impl true
def resource_module, do: MyAppArticle
# etc., etc.
end
defmodule MyAppWeb.Router do
# ...
scope "/", MyAppWeb do
# ...
# Configure using an :on_mount hook
live_session :my_app_session, on_mount: Permit.Phoenix.LiveView.AuthorizeHook do
# The :live_action names provided here will be
live "/live/articles", ArticleLive.Index, :index
live "/live/articles/new", ArticleLive.Index, :new
live "/live/articles/:id/edit", ArticleLive.Index, :edit
live "/live/articles/:id", ArticleLive.Show, :show
live "/live/articles/:id/show/edit", ArticleLive.Show, :edit
end
end
end
In a similar way to configuring controllers, LiveViews can be configured with option keywords or callback implementations, thus let's omit lengthy examples of both.
Most options are similar to controller options, with socket
in place of conn
.
Note that it is mandatory to implement the fetch_subject
callback, so it is recommended to put it as shared configuration in your web app module.
defmodule PermitTestWeb.ArticleLive.Index do
use MyAppWeb, :live_view
use Permit.Phoenix.LiveView,
authorization_module: MyApp.Authorization,
resource_module: MyApp.Article
@impl true
def mount(_params, _session, socket) do
# If the :index action is authorized, @loaded_resources assign
# will contain the list of accessible resources (maybe empty).
#
# Pagination, etc. can be configured using base_query/1 callback.
end
@impl true
def handle_params(params, _url, socket) do
# If assigns[:live_action] has changed, authorization and preloading occurs.
#
# If authorized successfully, it is assigned into @loaded_resource or
# @loaded_resources for singular and plural actions, respectively.
#
# If authorization fails, the default implementation of handle_unauthorized/2
# does:
# {:halt, push_redirect(socket, to: "/")}
# Alternatively you can implement a callback to do something different,
# for instance you can do {:cont, ...} and assign something to the socket
# to display a message.
end
@impl true
def fetch_subject(_socket, session) do
# Retrieve the current user from session
end
end