Sheriff
A simple minimal-dependency way to manage policy based authorization.
Installation
If available in Hex, the package can be installed as:
Add
sheriff
to your list of dependencies inmix.exs
:def deps do [{:sheriff, "~> 0.2"}] end
Ensure
sheriff
is started before your application:def application do [applications: [:sheriff]] end
Current User
Sheriff defaults to looking in Plug.Conn.private
for :current_user
, but this may not be compatible with all appliaction so we can configure the key:
config Sheriff,
resource_key: :your_desired_resource_key
Resource Loading
Resource loaders are responsible for retrieving the targetted resource provided for a specific request. A global loader can be can be specified in your application configuration or individual loaders can be supplied on a per plug basis.
Sheriff ships with a convenient Sheriff.ResourceLoader
behaviour:
defmodule Example.UserLoader do
@behaviour Sheriff.ResourceLoader
def fetch_resource(:show, %{"id" => id}), do: Repo.get(User, id)
def fetch_resource(:index, _params), do: Repo.all(User)
end
Policies
In Sheriff authorization is handled with policies which are modules that implement the Sheriff.Policy
behaviour:
defmodule Example.UserPolicy do
@behaviour Sheriff.Policy
alias Example.User
# Admins can see all the things!
def permitted?(%User{role: "admin"}, _request, _resource), do: true
# Users can access themselves
def permitted?(%User{id: id}, _request, %User{id: id}), do: true
# Team admin can view team members
def permitted?(%User{role: "team_admin", team_id: id}, :show, resources) do
Enum.all?(resources, &(&1.team_id == team_id)
end
# Not match, no access
def permitted?(_, _, _), do: false
end
Plugs
There are two plugs that serve as the workhorses of Sheriff, these need to occur after Plug.Parser
:
Sheriff.Plug.LoadResource
- Uses the configuredResourceLoader
to fetch the target resourceSheriff.Plug.EnforcePolicy
- Apply a givenPolicy
against the current user, target resource, and request.
When defining our authorization pipeline we could use something like this:
plug Sheriff.Plug.LoadResource, loader: Example.UserLoader
plug Sheriff.Plug.EnforcePolicy, policy: Example.UserPolicy
Error Handling
Sheriff has just three error scenerios we need to address:
- The requested resource is missing
- The current user is not authenticated
- The current user is not authorized to perform the requested action
To handle these cases we’ll want to provide an error handler for Sheriff. Our handler can be and module that
implements resource_missing/1
, unauthenticated/1
, and unauthorized/1
; the Sheriff.Handler
behaviour is optional.
Sheriff makes no assumptions so we need to tell it which module to use as a handler:
config Sheriff,
handler: YourApp.ErrorHandler
That’s it!