Authorization packages

authentication

#1

So, I’m starting to evaluate the main Elixir authorization packages for use in Phoenix, and a quick query on hex reveals this list:

authorize - Rule based authorization for Elixir

bodyguard - Bodyguard is a simple, flexibile authorization library with a focus on Phoenix 1.3+ apps.

canada - A DSL for declarative permissions

canary - An authorization library to restrict what resources the current user is allowed to access, and load those resources for you.

policy_wonk - Plug based authorization and resource loading. Aimed at Phoenix, but depends only on Plug. MIT license Updated to compile clean with Elixir 1.4

As well as OvermindDL1’s
permission_ex - Permission management and checking library for Elixir.

Just curious what people’s experience here have been for ease of use and performance.

For my purposes, I’m mainly interested in relatively coarse-grained permissions.

…for some trivia of the day, here in Japan we refer to this as:

Authorization = 承認 (shonin)
Authentication = 認証 (ninsho)


#2

This is purely a checking library currently. I keep permissions in the database and combine them from there and other sources (LDAP, an oracle database) to make a structure that is cached for short periods of time that I then rapidly do comparisons to using this library. It does nothing else, but it does that well. :slight_smile:

For Authentication I use uberauth though. For authorization I have a custom built thing that uses my permission_ex internally. I do things like:


  def report(conn, %{"report" => "facesheets" = report} = params) do
    conn
    |> can(%Permissions.Report{action: :index, report: report})
    ~> case do conn ->
      ...
    end
  end

Potentially multiple times, like checking that this person is allowed to access each row of a given returned table from a query and filtering it out (I have a can?/2 variant that just returns a boolean, where can/2 returns either the original passed in ‘environment’, many different environments are supported, or it returns an exception, yes returns not throws, the ~> is from expede’s exceptional library that only runs/pipes-into it’s second argument if it is not an exception structure). It’s fairly simple overall but has a massive amount of detail and refinement of access permissions. I can show individual columns for individual student for different students for different users and such, highly detailed. I did not find any library that supported such detail at the time I made it, and I’m unsure about the recently made libraries. ^.^;


#3

There is a big thread on auth here:

It’s actually our most viewed thread to date :slight_smile:


#4

Yep, that is an awesome thread. I probably spent hours pouring over it.

I’m also interested in possibly using GraphQL. While looking into it, it appears GraphQL really doesn’t have an opinion about authZ (or authN, for that matter).

While researching this more, I found these talks really helpful:

Facebook GraphQL Stack.
Dan Schafer at FB, react-europe 2016

Tony Ghita - Twitch’s GraphQL Transformation

The basic points of both talks seems to be:

*GraphQL wasn’t Facebook’s first API, they had other ones.
*Having all of the business logic live underneath the API layer, and the API layer being a thin layer on top of that, proved to be a robust architecture for Facebook.
*Push authorization out of GraphQL/REST, into a different layer.
*Authorization rules should emerge from your business logic.

One implication is, there are lots of great libraries and protocols for the hard stuff in authN. But for authZ, it might be harder to use a generic library as-is, since that is really getting into the specifics of your application and its unique business logic.


#5

Hi, I build my own authorization code. So, for my needs, I have 3 kinds of authorization:

  1. Role authorization (specific roles for specific actions, admin or customer service can look users or orders data, etc).
  2. Ownership authorization (user can only reads and updates his/her own orders, profile, password, posts, etc).
  3. Combination of above.

I previously used canary, but now I just write my own authorization code.

I have this very first iteration of code. There are 3 public functions. Note, that I previously set current user and its role beforehand (via a plug). (I have a user table and a role table, user table has role_id).

verify_role, to verify if user has a role included as permitted roles.
verify_owner, to verify ownership of user’s data.
verify_owner_or_role, combination of above, just for convenience.

They all return {:ok}, if verified, otherwise they will return {:error, status}, which will be received by FallbackController.

For my case, I prefer checking the authorization on the controller. Something like this:

def show(conn, %{"id" => id}) do
  with {:ok} <- verify_owner_or_role(conn, id, ["admin", "customer_service"]) do
    user = Accounts.get_user!(id)
    render(conn, "show.json", user: user)
  end
end

It means that to show a specific user data with id 1, conn must have current_user to be the user with id 1, OR current_user is an admin or a customer service. Your case may vary.

defmodule WshopWeb.ApiSecurity.UserVerification do
  def verify_role(conn, permitted_roles) do
    with {:ok, conn} <- authenticate(conn),
         {:ok, conn} <- authorize(conn, permitted_roles) do
      {:ok}
    else
      {:error, status} ->
        {:error, status}
    end
  end

  def verify_owner_or_role(conn, user_id, permitted_roles) do
    case verify_role(conn, permitted_roles) == {:ok} || verify_owner(conn, user_id) == {:ok} do
      true -> {:ok}
      false -> {:error, :forbidden}
    end
  end

  def verify_owner(conn, user_id) do
    with {:ok, conn} <- authenticate(conn),
         {:ok, conn} <- do_verify_ownership(conn, user_id) do
      {:ok}
    else
      {:error, status} ->
        {:error, status}
    end
  end

  defp do_verify_ownership(conn, user_id) do
    case conn.assigns.current_user.id == user_id do
      true -> {:ok, conn}
      false -> {:error, :forbidden}
    end
  end

  defp authenticate(conn) do
    if conn.assigns.current_user do
      {:ok, conn}
    else
      {:error, :unauthorized}
    end
  end

  defp authorize(conn, permitted_roles) when permitted_roles == [], do: {:ok, conn}

  defp authorize(conn, permitted_roles) do
    case role_permitted?(conn, permitted_roles) do
      true -> {:ok, conn}
      false -> {:error, :forbidden}
    end
  end

  defp role_permitted?(conn, permitted_roles) do
    assigns = conn.assigns
    Map.has_key?(assigns, :role) && assigns.role in permitted_roles
  end
end

There are also 2 other cases:

  1. For a case which a model can be updated by different user roles, like in Uber/Lyft case, when an order is placed by a customer who can also change the state of the order (create, cancel, etc) but the states (from pickup to finished) are updated by a driver. I could write another function, but I haven’t. Something like verify_picked_order. I should have renamed above module to be like UserJourneyVerification while that verify_picked_order could be placed in another module. Note that code above is my first iteration, I should split the authorization for admin/customer service or whatever other than the customer, into another module.
  2. For a case of which an authorization is done on grouping. Let say a teacher can look into his students data. I should create verify_role_and_owner, which means a teacher must have a role of teacher (or whatever the business requirements demand) and we query the database to check if a teacher teach specific students, so we can show the students data.

I don’t know if this approach is good or not. I like this approach, it is just a group(s) of functions.

Critics and suggestions are welcome.


#6

@dwahyudi, I found what should be the most flexible and powerful model reading the code in python, java, node, etc… of this lib: https://casbin.org/ . Let me know if you and others are willing to join and write in elixir.