Permit.Phoenix
Phoenix Framework and LiveView integration for Permit authorization library.
Purpose and usage
Permit.Phoenix allows for consistent authorization of actions throughout the entire codebase of a Phoenix application, both in Plug-based controllers and in LiveView.
Permitprovides the permission definition syntaxPermit.Ectois optional, but - if present - it constructs queries to look up accessible records from a database, based on defined permissionsPermit.Phoenixplugs into controllers and live views in order to automatically preload records and check authorization permissions to perform actions.
Installation
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
Controllers
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.
One-off usage
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
Global usage with settings in specific controllers
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
LiveView
Router configuration
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
LiveView configuration
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