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.

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.