AuthZ

AuthZ is a simple authorization library that allows you to

  • ensure users are permitted to access protected routes;
  • encourage authorization rules in contexts.

Implementing authorization rules in contexts

Authorization is easy to implement using the power of pattern matching. Below is an example where only authors are authorized to edit their posts.

use AuthZ.Authorizer simply injects a behaviour, requiring your module to implement authorize/3. This function should receive an atom describing the action, the logged in user and the resource intended to be accessed; the function should return :ok in case the action is authorized, or a tuple containing :error as its first element and an atom describing the reason of unauthorized access as its second element (allowing to return different possible reasons of failure allows the controller for example to send more specific error messages to the logs or the view).

defmodule MyApp.Post.Policy do
  use AuthZ.Authorizer

  alias MyApp.Accounts.User
  alias MyApp.Blog.Post

  @unauthorized {:error, :unauthorized}

  def authorize(:edit_question, %User{id: user_id}, %Post{author: user_id}) do
    :ok
  end

  def authorize(:edit_question, _, _) do
    @unauthorized
  end
end

use AuthZ.Authorizer also injects the function authorized?/3 which simply calls the user-defined function authorize/3 and returns a boolean indicating authorization success or failure.

Protecting routes against unauthorized users

Authorization can be enforced for some routes. Create a module using (use) the AuthZ.AuthorizationPlugMixin module; then implement the callbacks handle_authentication_error/2 and handle_authorization/3. Both callbacks receive a Plug.Conn struct and an atom identifying the set of routes that require authentication; handle_authorization/3 additionally receives the logged in user.

defmodule MyAppWeb.Plugs.EnsureAuthorized do
  use AuthZ.AuthorizationPlugMixin

  import Plug.Conn
  import Phoenix.Controller

  alias MyApp.Accounts.User

  def handle_authentication_error(conn, :admin_routes),
    do: conn |> put_status(401) |> text("unauthenticated") |> halt()

  def handle_authorization(conn, %User{type: "admin"}, :admin_routes),
    do: conn

  def handle_authorization(conn, _, _),
    do: conn |> put_status(403) |> text("unauthorized") |> halt()
end

You may then use the new plug into a pipeline and ensure that routes requiring authorization are accessed by authorized users only.

pipeline :ensure_admin_routes_authorized do
  plug MyAppWeb.Plugs.EnsureAuthorized,
    resource: :admin_routes
end

scope "/admin", MyAppWeb, as: :admin do
  pipe_through [:browser, :ensure_admin_routes_authorized]
  # code
end

Installation

Add auth_z for Elixir as a dependency in your mix.exs file:

def deps do
  [
    {:auth_z, "~> 0.2.0"}
  ]
end

HexDocs

HexDocs documentation can be found at https://hexdocs.pm/auth_z.