Provide a single source of truth of action permissions throughout your codebase, making use of Ecto to have your Phoenix Controllers and LiveViews authorize access to resources without having to repeat yourself.
Permit supports multiple integration points across the Elixir ecosystem:
- Phoenix Controllers & LiveView - with support for LiveView 1.0 and Streams
- GraphQL APIs - through Absinthe integration (experimental)
- Custom integrations - extensible architecture for other frameworks
Required package: :permit.
defmoduleMyApp.AuthorizationdousePermit,permissions_module: MyApp.PermissionsenddefmoduleMyApp.PermissionsdousePermit.Permissions,actions_module: Permit.Phoenix.Actionsdefcan(%{role: :admin}=user)dopermit()|>all(MyApp.Blog.Article)enddefcan(%{id: user_id}=user)dopermit()|>all(MyApp.Blog.Article,author_id: user_id)|>read(MyApp.Blog.Article)# allows :index and :showenddefcan(user),do: permit()endRequires :permit_phoenix package, and optionally :permit_ecto for sourcing authorization data from the DB.
defmoduleMyAppWeb.Blog.ArticleControllerdouseMyAppWeb,:controllerusePermit.Phoenix.Controller,authorization_module: MyApp.Authorization,resource_module: MyApp.Blog.Article# assumption: current user is in assigns[:current_user] - this is configurabledefshow(conn,_params)do# At this point, the Article has already been preloaded by Ecto and checked for authorization# based on action name (:show).# It's available as the @loaded_resource assign.render(conn,"show.html")enddefindex(conn,_params)do# The list of Articles accessible by current user has been preloaded by Ecto# into the @loaded_resources assign.render(conn,"index.html")end# Optionally, implement the handle_unauthorized/1 callback to deal with authorization denial.endRequires :permit_phoenix, and optionally :permit_ecto.
defmoduleMyAppWeb.RouterdousePhoenix.RouterimportPhoenix.LiveView.Routerlive_session:authenticated,on_mount: Permit.Phoenix.LiveView.AuthorizeHookdolive("/articles",MyAppWeb.Blog.ArticlesLive,:index)live("/articles/:id",MyAppWeb.Blog.ArticlesLive,:show)endenddefmoduleMyAppWeb.Blog.ArticleLivedousePhoenix.LiveViewusePermit.Phoenix.LiveView,authorization_module: MyApp.Authorization,resource_module: MyApp.Blog.Article,use_stream?: true# Enable LiveView 1.0 Streams support@impltruedeffetch_subject(session),do: # load current user# Both in the mount/3 callback and in a hook attached to the handle_params event,# authorization will be performed based on assigns[:live_action].# With streams enabled, :index actions will use streams instead of assigns.# Optionally, implement the handle_unauthorized/1 callback to deal with authorization denial.endRequires :permit_absinthe, whereas :permit_ecto is automatically retrieved to provide Dataloader support - see Permit.Absinthe docs.
defmoduleMyAppWeb.SchemadouseAbsinthe.SchemausePermit.Absinthe,authorization_module: MyApp.Authorizationobject:articledopermitschema: MyApp.Blog.Articlefield:id,:idfield:title,:stringfield:content,:stringfield:author_id,:idendquerydofield:article,:articledopermitaction: :readarg:id,non_null(:id)resolve&load_and_authorize/2# Automatically loads and authorizes based on permissionsendfield:articles,list_of(:article)dopermitaction: :readresolve&load_and_authorize/2# Returns only articles accessible by current userendendmutationdofield:create_article,:articledopermitaction: :createarg:title,non_null(:string)arg:content,non_null(:string)# Use middleware for complex authorization scenariosmiddlewarePermit.Absinthe.Middleware.LoadAndAuthorizeresolvefn_,args,%{context: %{current_user: user}}->MyApp.Blog.create_article(user,args)endendendendRequires :permit for the basic checks, and :permit_ecto for accessible_by!/3.
# Check permissions directlycan(current_user)|>update?(article)# Generate Ecto queries based on permissionsMyApp.Authorization.accessible_by!(current_user,:read,Article)# Use the friendly API for multiple actionscan(current_user)|>do([:read,:update],article)Permit is designed as a modular ecosystem with multiple packages:
| Package | Version | Description |
|---|---|---|
| permit | Core authorization library | |
| permit_ecto | Ecto integration for database queries | |
| permit_phoenix | Phoenix Controllers & LiveView integration | |
| permit_absinthe | GraphQL API authorization via Absinthe |
Version 0.3.3 fixed a bug in the predicate function behaviour with has-many associations not matching Permit.Ecto behaviour.
Version 0.3.2 fixed a bug in the predicate functions respecting action grouping.
Version 0.3.1 fixed a bug in the loader function.
Version 0.3 brought several major improvements:
- Phoenix LiveView 1.0 support with Streams for managing large collections
- Router-based action inference - automatically derive action names from Phoenix routes
- Friendly
can(user) |> do(action, resource)API for more readable permission checks - Enhanced performance and better error handling
See our recent blog posts for more details:
- Updates to Permit and Permit.Phoenix, announcing Permit.Absinthe
- Future of Permit authorization library
An outline of our development goals for both the "MVP" and further releases.
The following features of Permit (along with its companion packages), originally intended as an initial backlog, has already been fulfilled:
- Rule definition syntax
- Defining rules for Create, Read, Update and Delete actions
- Defining rules for arbitrarily named actions
- Authorization resolution
- Authorizing a subject to perform a specific action on a resource type (i.e. struct module, Ecto schema)
- Authorizing a subject to perform a specific action on a specific resource (i.e. struct, loaded Ecto record)
- Ecto integration
- Loading and authorizing a record based on a set of params (e.g. ID) and subject
- Building Ecto queries scoping accessible records based on subject and resource type
- Phoenix Framework integration
- Authorizing singular resource actions (e.g.
show,update)- Plug / Controller
- LiveView
- Preloading record (based on params) in singular resource actions and authorizing the specific record
- Plug/Controller
- LiveView
- Authorizing non-singular resource actions (e.g.
index)- Plug/Controller
- LiveView
- Preloading accessible records in non-singular resource actions (e.g.
index)- Plug/Controller
- LiveView
- Authorizing singular resource actions (e.g.
- Documentation
- Examples of vanilla usage, Plug and Phoenix Framework integrations
- Thorough documentation of the entire public API
- Dependency management
- Introduce
permit_ectoandpermit_phoenixlibraries providing the possibility of using the library without unneeded dependencies
- Introduce
This list of planned items relates to the main Permit repository as well as to Permit.Ecto, Permit.Phoenix, Permit.Absinthe and possible future offspring repositories related to the Permit project.
- Performance & Optimization
- Compile-time optimizations and caching
- Static code analysis for authorization rules
- Policy playground & visualization tools
- Code generators for common authorization patterns with Permit.Ecto
- Extended Framework Support
- Ash framework integration
- Commanded (CQRS/ES) integration
- Absinthe integration - in progress
- Phoenix route-based authorization
- Research Ideas
- Explore feasibility of entity field-level authorization
- Research alignment of Permit with PostgreSQL RLS
The easiest way to set up Permit is with Igniter, which will add the dependencies, generate authorization modules, and patch your web module automatically:
# Full setup with Ecto and Phoenix (LiveView + controllers)
mix igniter.install permit --phoenix
# With Absinthe/GraphQL integration
mix igniter.install permit --phoenix --absinthe
# Base Permit only, no Ecto
mix igniter.install permit --no-ectoAfter installation, use the patch tasks to wire Permit into existing controllers and LiveViews:
mix permit.patch.controller MyAppWeb.ArticleController MyApp.Blog.Article
mix permit.patch.live_view MyAppWeb.ArticleLive.Index MyApp.Blog.ArticleThe :igniter dependency is required to run these tasks from within the project. See the Igniter documentation for more details.
Alternatively, add permit to your list of dependencies in mix.exs:
defdepsdo[{:permit,"~> 0.4.1"}]endFor additional integrations, add the relevant packages:
defdepsdo[{:permit,"~> 0.4.1"},{:permit_ecto,"~> 0.3.0"},# For Ecto integration{:permit_phoenix,"~> 0.5.0"},# For Phoenix & LiveView{:permit_absinthe,"~> 0.3.0"},# For GraphQL (Absinthe){:igniter,"~> 0.8",only: [:dev,:test]}# For generator tasks]end- Core library: hexdocs.pm/permit
- Ecto integration: hexdocs.pm/permit_ecto
- Phoenix integration: hexdocs.pm/permit_phoenix
- Absinthe integration: hexdocs.pm/permit_absinthe
We welcome contributions! Please see our Contributing Guide for details.
Just clone the repository, install dependencies normally, develop and run tests. When running Credo and Dialyzer, please use MIX_ENV=test to ensure tests and support files are validated, too.
- A Framework for Unified Authorization in Elixir, M. Buszkiewicz, Curiosum Elixir Meetup #5, May 2022
- Permit - An Uniform Authorization Library for Elixir, M. Buszkiewicz, Curiosum Elixir Meetup #8, August 2022
- Authorization & Access Control: Case Studies and Practical Solutions using Elixir, ElixirConf EU, May 2025 - publicly available on YouTube soon
- Introducing Permit: An Authorization Library for Elixir, Curiosum, August 2022
- Authorize access to your Phoenix app with Permit, Curiosum, October 2023
- Updates to Permit and Permit.Phoenix, announcing Permit.Absinthe, Curiosum, Jun 2025
- Future of Permit authorization library, Curiosum, Jun 2025
- Slack channel: Elixir Slack / #permit
- Issues: GitHub Issues
- Discussions: GitHub Discussions
- Blog: Curiosum Blog
- Library maintainer: Michał Buszkiewicz
- Curiosum - Elixir development team behind Permit
This project is licensed under the MIT License - see the LICENSE file for details.
