Can

Dead simple, fire and forget authorization kit for the Phoenix framework

Build Status

Installation

Add Can to your list of dependencies in mix.exs:

def deps do
[{:can, "~>0.0.2"}]
end

Usage

Generally, there are two things you need to explicitly implement in your application.

For this controller and action

defmodule MyApp.PageController do
def show(conn, %{"id" => id}) do
page = Repo.get(Page, id)
render(conn, "show.html", page: page)
end
end
  1. You will first need to define your policy by mirroring your controller naming convention and it's action
def MyApp.PagePolicy do
def show(conn, page) do
conn.assign.current_user.id == page[:author_id]
end
end
  1. Use the can macro and add an unauthorized_handler
defmodule MyApp.PageController do
use MyApp.Web, :controller
use Can, :unauthorized_handler
def show(conn, %{"id" => id}) do
page = Repo.get(Page, id)
can(conn, page) do
render(conn, "show.html", page: page)
end
end
def unauthorized(conn, resource, policy) do
conn
|> put_flash(:error, "You are unauthorized because #{policy} did not return true for author id #{resource[:author_id]}")
|> render("show.html", page: resource)
end
end

Alternative Handler

The unauthorized handler can also be done in a separate module if you wish so.

This effectively separates the handler and the controller, and makes pattern matching against the policy clean and readable

defmodule MyApp.PageController do
use MyApp.Web, :controller
use Can, {MyApp.UnauthorizedHandler, :unauthorized}
def show(conn, %{"id" => id}) do
page = Repo.get(Page, id)
can(conn, page) do
render(conn, "show.html", page: page)
end
end
end
defmodule MyApp.UnauthorizedHandler do
import Phoenix.Controller
def unauthorized(conn, resource, PagePolicy) do
conn
|> put_flash(:error, "You are unauthorized because #{policy} did not return true for author id #{resource[:author_id]}")
|> render("show.html", page: resource)
end
# wildcard
def unauthorized(conn, _resource, policy) do
conn
|> put_flash(:error, "You are unauthorized because #{policy} did not return true")
|> render(MyApp.ErrorView, "401.html")
end
end

You can write your own authorization logic as complex or as simple as you wish. It is necessary however, at the end of your authorization logic, it has to return a boolean value.

In the case when the authorization logic returns true, the connection proceeds normally. If the authorization logic return false, the unauthorized handler will be called instead.

Documentation

See documentation on hexdocs for API reference and usage details.