Authorization with Elixir and Plug

This is a walkthrough of building simple user authorization with
Plug. Plug is a system for composable
modules for web applications. In this example, I am
building a system explicitly for use in a Phoenix web application, but the
approach can easily be modified to work in other contexts. You can find this
approach implemented [here] (https://github.com/coshx/phx_oembed).

What is Plug?

Plug is an Elixir library and is used in Phoenix and
other Elixir-based web applications and frameworks. To be more specific, Plug is
(from the Github page):

  1. A specification for composable modules between web applications
  2. Connection adapters for different web servers in the Erlang VM

If you have experience with Ruby on Rails, you may find that Plug is
similar to Rack, and that writing Plugs is similar to writing Rack
middleware. However, the similarities are not very deep, and it’s worth the time
to get to know Plug by itself.

Using Plug in a Phoenix web application allows us to write functional components
that do useful things as part of the request and response cycle. One of those
useful things is authorize user actions.

Goals

Coming from a Rails background, I deal with user authorization a lot. I’ve used
CanCan in some larger projects, but recently have been using
Pundit.
I enjoy Pundit because of it’s simplicity and lack of magic. While there are a few
awkward places (Policy Scopes), I generally find it very nice to use.

I’ve been building an Oembed server in
Phoenix as a side project, and when it
came time to handle user authorization, I looked for something simple like
Pundit. Unfortunately, the solutions I found were closely coupled with user
authentication or just wouldn’t work for my case. Since
I prefer to keep user authorization distinct from authentication, I decided to
build my own system as an experiment.

My main goal was to create something as easy to use and
simple as Pundit, but using Elixir. Since Elixir is a functional language, and
Pundit’s architecture is inspired by object-oriented design, it was not
immediately clear how that would work. So I broke my goals down further:

  1. Agnostic of user authentication system.
  2. As lightweight and simple as possible.
  3. Users would specify authorization rules by supplying one function per
    controller action.
  4. Easily testable.
  5. Safe defaults.

Implementation

In Phoenix, connections are handled via a
struct called conn.
Plugs will accept a conn struct, possibly modify it, and then return a new
conn struct. Plugs can also terminate a request via the halt/1 function.
The conn struct contains lots of information about the request:
the controller, the action, route, parameters, etc.

I implemented authorization as a series of Plugs. The first step was to write two
Plugs that would add two attributes to the conn struct: the current user, and the
resource to use when authorizing the action. Combined with existing information
in the conn struct, I could use this new information to write a Plug that would
either authorize the action or halt the connection.

AssignGuardianUser

First I wrote a plug that would expose the current user via
conn.assigns[:user]. I’m using Guardian with JWT for authentication, and the
current user was hidden away in a non-standard place in the conn struct. So
this Plug, and tests, were straightforward, in that I simply had to copy the
current users’ information from where Guardian put it to a more standard place
in the assigns space.

defmodule PhxOembed.Plugs.AssignGuardianUser do
  import Plug.Conn

  def init(default), do: default

  def call(conn, _) do
    assign(conn, :user, conn.private.guardian_default_resource)
  end
end

Now, I can just add this Plug to my pipelines in my router file to make my
current user object available in a standard place in all my actions.

plug PhxOembed.Plugs.AssignGuardianUser

The next step was to write a Plug that would expose the authorizing resource in
a standard place. The idea of an “authorizing resource” is something I came up
with when using Pundit with Rails. It’s simple – each controller action is
authorized based on the combination of the current user and an authorizing
resource. The authorizing resource is not necessarily the resource being
retrieved or manipulated by the controller action.

For example, let’s say we are building an invoicing system that has many
Customers. Each Customer has many Invoices and can have many Users. We
need to figure out if a User can view an Invoice or not. In this case,
the authorizing resource would be the Customer record that is associated with
both the Invoice and the current User, not the Invoice itself. I find this approach a
lot simpler than forcing authorization to be tied to the resource being acted
upon, as Pundit tries to do by default. It also has a benefit of more neatly
dealing with index actions and other actions that work with collections of things.

Here is the Plug to assign the authorizing resource:

defmodule PhxOembed.Plugs.AssignAuthorizingResource do
  import Plug.Conn

  def init(default), do: default

  def call(conn, %{resource: resource, resource_id: resource_id}) do
    resource_id = conn.params[resource_id]

    if resource_id == nil do
      resource = %{}
    else
      {resource_id, _} = Integer.parse(resource_id)
      resource = PhxOembed.Repo.get(resource, resource_id)
    end

    assign(conn, :authorizing_resource, resource)
  end
end

This plug receives the module name of the resource in question, plus the name
of the request parameter that contains the actual resource id. Then, it
extracts the id from the conn struct, looks up the resource, and attaches it to
a new conn struct. This plug needs to be used in a controller, so you can
specify which resource type and ID to use. For example, here is how I’m using
this in my CardController. In this case, the authorizing resource is the
associated Site (a Card belongs to a Site):

plug PhxOembed.Plugs.AssignAuthorizingResource,
      %{resource: Site, resource_id: "site_id"}

Now we have the basics set up — two Plugs that attach both the current user and
the authorizing resource to a consistent location on the conn struct. Now all
we need to do is pass the conn struct to an authorization Plug, implement
logic for each action, and we are set.

We can accomplish this using Elixir’s pattern matching and multiple function
clauses by defining a single authorizing function, and passing it the
conn struct along with the current controller name and action. Then we can
write definitions for different clauses based on the controller and action.

Here is my authorizing Plug:

defmodule PhxOembed.Plugs.Authorization do
  import Plug.Conn

  def init(default), do: default

  def call(conn, _) do
    conn |> authorize(conn.private[:phoenix_controller], conn.private[:phoenix_action])
  end

  defp authorize(conn, PhxOembed.Api.SiteController, :show) do
    conn |> authorize_by_resource
  end

  defp authorize(conn, PhxOembed.Api.SiteController, :index) do
    assign(conn, :authorization_performed, true)
  end

  defp authorize(conn, PhxOembed.Api.SiteController, :create) do
    assign(conn, :authorization_performed, true)
  end

  defp authorize(conn, PhxOembed.Api.CardController, :create) do
    conn |> authorize_by_resource
  end

  defp authorize(conn, PhxOembed.Api.CardController, :index) do
    conn |> authorize_by_resource
  end

  defp authorize(conn, PhxOembed.Api.CardController, :update) do
    conn |> authorize_by_resource
  end

  defp authorize(conn, PhxOembed.Api.CardController, :delete) do
    conn |> authorize_by_resource
  end

  defp authorize(conn, _, _) do
    conn
    |> assign(:authorization_performed, true)
    |> send_resp(403, "Forbidden.")
    |> halt
  end

  defp authorize_by_resource(conn) do
    if (conn.assigns[:user].id == conn.assigns[:authorizing_resource].user_id) do
      assign(conn, :authorization_performed, true)
    else
      conn
      |> assign(:authorization_performed, true)
      |> send_resp(403, "Forbidden.")
      |> halt
    end
  end
end

Here I have a different function definition for each controller action, similar
to what is done with Pundit. I prefer this explicit, declaritive style. I also
have a clause that matches any controller and action, which provides safe
defaults in case I forget to include this Plug somewhere. Finally, I refactored
my actual authorizing logic into a private method. As you can see, the Plug only
deals with two cases — figuring out if the user is associated with the
authorizing resource, and allowing all authenticated users.

Also, note that I am adding an authorization_performed boolean to the conn
struct. This is so I can add one final Plug that will throw an error if I forget to
authorize an action or a controller. This got a little hacky, as I’m still
pretty new to Elixr, but it does it’s job.

defmodule PhxOembed.Plugs.VerifyAuthorized do

  defexception message: "Authorization not performed"

  def init(default), do: default

  def call(conn, _) do
    try do
      unless conn.assigns.authorization_performed == true do
        raise PhxOembed.Plugs.VerifyAuthorized
      end
    rescue _ in KeyError -> raise PhxOembed.Plugs.VerifyAuthorized
    end

    conn
  end
end

This is pretty much the entire framework. I wrote tests for these Plugs, you can
see them here.
Also, I started writing integration tests for the whole framework, you can see
those
here.

Improvements

I would like to make a couple of improvements to this approach:

  1. Currently the AssignAuthorizingResource Plug makes a database call, which
    could easily lead to duplicate/unecessary database round trips (for example,
    if the controller needs to also look up the authorizing resource). It would
    be good to refactor this Plug to somehow avoid a database lookup.

  2. the VerifyAuthorized Plug is awkard, and must be included in each
    controller after authorization is done, which diminishes the usefullness.
    I’d like to include this Plug in the router pipelines instead.