Authorization with Phoenix

I’m designing an API with Phoenix Framework, I managed to achieve authentication with Guardian library. The API is JWT based and should be completely stateless about authentication mechanisms, mentioning a correctly implemented JWT.

Now, I need a run-time based authorization system, it may be “role-based” or not. I found two libraries called canary and can, but these are tend to be compile-time authorization libraries. I don’t need something with policies, exclusions and other complicated stuff, but something more pluggy and mostly relies on storage like Redis or Postgres, so I can alter in run-time.

Also, one other problem of the mentioned libraries is they are fetching user from database opinionatedly. They are authorizing requests after the request handler, which seems redundant to me. I will not apply an authorization algorithm relying on per entity basis.

Any ideas will be appreciated, thank you.

What do you mean by compile-time? I don’t use canary but I do use canada (which is what can is from) and I use it with my PermissionEx library for the ‘permission’ testing. I have a database table that holds the permissions (a PostgreSQL map/jsonb column) and a cacheing layer that holds permissions for a couple of minutes at a time and I use the can? lookup to do the actual testing. I could just as easily do something like canary and test it in a plug but I often do not know all the roles that need to be tested in detail until in my controller, thus combined with the Happy library allows me to do early-out on permission failures. I.E. it makes my controllers look like:

  def edit(conn, %{"slug" => slug_param}) do
    happy_path!(else: handle_error(conn)) do
      @perm true = conn |> can?(edit(%Perms.CheckInOut{section: true}))
      {:ok, section} = verify_single_record get_section_by_slug(slug_param)
      @perm true = conn |> can?(edit(%Perms.CheckInOut{section: section.slug}))
      changeset = CheckInOut.Section.changeset(section)
      render(conn, :edit, changeset: changeset, section: section)

First I check to if they have edit permission on any kind of section at all, if true then I get the section via its slug, I then test the normalized slug from the DB to another permission to see if they have ‘edit’ permission for this specific section, if so etc… If any of those fail then the happy_path else clause handles my errors, which for a permission failure calls this clause of that function:

  def handle_error({:perm, false}, conn)              ,do: do_unauthorized(conn)

I have a ton of handle_error clauses for a variety of issues. It makes the controllers short, simple, etc… I quite like the style. :slight_smile: