Joseph Kain bio photo

Joseph Kain

Professional Software Engineer learning Elixir.

Twitter LinkedIn Github

As I’m mentioned before I have an ongoing project being developed using Elixir and Phoenix. I’m using two third party packages that I wanted to write about in this post. They are Guardian and Canary.

User Authentication and Authorization

In my app I’m using Guardian for for authentication and session management. Guardian has a lot of interesting functionality but at the moment I’m using it for simple password based authentication of users. And, for authorization I’m using the Canary library.

If you aren’t familiar with these two terms I’ll explain.

I use authentication to manage logins and sessions. The user authenicates him or herself by providing a user name and password. Once authenticated a session is created on the user’s behalf.

Once logged in the user has a certain set of privileges. For example, a user may be allowed to modify certain resources that he or she owns. But that same user may not be allowed to modify resources that belong to other users. Canary checks to see if a user is authorized to cary out the actions that he or she requests.

Guardian Setup

My Guardian configuration is pretty simple. I wrote up these pipelines for my router for Guardian:

pipeline :browser_session do
  plug Guardian.Plug.VerifySession
  plug Guardian.Plug.LoadResource
end

pipeline :require_login do
  plug Guardian.Plug.EnsureAuthenticated, handler: MyApp.GuardianErrorHandler
end

Then I have created scopes that either support a logged in user or require a logged in user. For example:

scope "/", MyApp do
  pipe_through [:browser, :browser_session]

  get "/", PageController, :index

  resources "/search",  SearchController, only: [:index, :show]
end

This scope user the :browser_session pipeine to load a session if one exists. But it doesn’t require a session. This way both logged in users or guest users can browse the home page and search.

But the resouces in this scope require a user to be logged in:

scope "/", MyApp do
  pipe_through [:browser, :browser_session, :require_login]

  resources "/rentals", RentalController do
    resources "/images", ImageController, only: [:create]
  end
end

Here, I add the :require_login pipeline which uses Guardian’s EnsureAuthenticated plug to require a session. Errors are passed off to my MyApp.GuardianErrorHandler module which is responsible for redirecting guest users to /login. This is a simple module that looks like this:

defmodule MyApp.GuardianErrorHandler do
  import MyApp.Router.Helpers

  def unauthenticated(conn, _params) do
    conn
    |> Phoenix.Controller.put_flash(:error,
                                    "You must be logged in to access that page.")
    |> Phoenix.Controller.redirect(to: login_path(conn, :new))
  end
end

My Canary Setup

I use canary in my controllers to check if a user has authorization to view or modify a given resource.

For example, above we saw routes that edit rentals and create images for those rentals. And that these routes require a logged in user. Well, we need more than just a logged in user. The user needs to own those resources.

Here’s an example of how I use Canary to authorize for Images:

defmodule MyApp.RentalController do
  use MyApp.Web, :controller

  alias MyApp.Rental

  plug :scrub_params, "rental" when action in [:create, :update]
  plug :authorize_resource, model: Rental

  # Actions follow ...
end

Canary’s :authorize_resource plug checks if the current user is allowed to act on the a Rental. This of course depends on a can? function. Here’s part of the can? function I’ve written:

def can?(user, action, Rental) when action in [:new, :create] do
  user.role == "owner"
end
def can?(user, action, rental = %Rental{}) do
  rental.owner_id == user.id
end

Here I decide if the user is authorized depending on the action.

Using Canary with Guardian

All of the above seems pretty great, but it doesn’t work!

One of the problems I had making Guardian and Canary play well together was that Canary depends on being able to find the current user in the connection. Guardian maintains the current user and it can be accessed by calling Guardian.Plug.current_resource(conn). But, Canary expects the current user in the :current_user assign in the conn.

To make this work I wrote up another small plug:

defmodule MyApp.Plug.CurrentUser do
  def init(opts), do: opts

  def call(conn, _opts) do
    current_user = Guardian.Plug.current_resource(conn)
    Plug.Conn.assign(conn, :current_user, current_user)
  end
end

This plug simply queries the current user from Guardian and put’s it into the :current_user assign in the conn. This copies the data from Guardian’s location to the location Canary expects.

I add this plug to my :require_login pipeline

pipeline :require_login do
  plug Guardian.Plug.EnsureAuthenticated, handler: MyApp.GuardianErrorHandler
  plug MyApp.Plug.CurrentUser
end

With this plug Canary can access :current_user as an assign and all is well. I also use the :current_user assign in my own code. My controllers don’t need to know about Guardian.

Next Steps

This post focused on how I use Guardian and Canary together to authenticate and authorize in my app. I covered all of my Guardian usage and what I had to do in order to use the two libraries together. But, there’s a bit more to my Canary usage that I hope to write about next week.