Sheriff

A simple minimal-dependency way to manage policy based authorization.

Installation

If available in Hex, the package can be installed as:

  1. Add sheriff to your list of dependencies in mix.exs:

    def deps do
      [{:sheriff, "~> 0.2"}]
    end
  2. 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:

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!