Home Posts Post Search Tag Search

Ash Framework 07 - Authorization: What Can You Do?
Published on: 2025-10-09 Tags: elixir, Side Project, LiveView, Ecto, Authorization, Html/CSS, Phoenix, Ash

Chapter 6: Authorization: What Can You Do?

We have the app in a good place, but permissions aren’t fully set up and the API doesn’t yet enforce authorization. We also want the app to behave differently when a user is logged in versus not logged in.


Introducing Policies

  • Policies in Ash define rules that determine who can perform what actions.
  • Each policy consists of:
    1. Policy conditions – determine when a policy applies.
    2. Policy checks – pass or fail depending on actor attributes or resource state.

Example: Blog site publish action:

defmodule Blog.Post do
  use Ash.Resource, authorizers: [Ash.Policy.Authorizer]

  policies do
    policy action(:publish) do
      forbid_if expr(published == true)
      authorize_if actor_attribute_equals(:role, :admin)
    end
  end
end
  • Policies are evaluated in order. If none succeed, the action fails.

Authorizing API Access for Authentication

User policy for registration/sign-in via LiveView:

policies do
  bypass AshAuthentication.Checks.AshAuthenticationInteraction do
    authorize_if(always())
  end

  policy action([:register_with_password, :sign_in_with_password]) do
    authorize_if(always())
  end
end

Authentication via JSON (Postman example headers):

Content-Type: application/vnd.api+json
Accept: application/vnd.api+json

Attach token metadata in lib/tunez/accounts.ex:

json_api do
  routes do
    post :register_with_password do
      route("/register")
      metadata(fn _subject, user, _request ->
        %{token: user.__metadata__.token}
      end)
    end

    post :sign_in_with_password do
      route("/sign_in")
      metadata(fn _subject, user, _request -> %{token: user.__metadata__.token} end)
    end
  end
end

GraphQL Setup:

mix ash.extend Tunez.Accounts.User graphql

Define mutations and queries:

graphql do
  mutations do
    create(Tunez.Accounts.User, :register_user, :register_with_password)
  end

  queries do
    get(Tunez.Accounts.User, :sign_in_user, :sign_in_with_password) do
      type_name :user_with_token
    end
  end
end

Assigning Roles to Users

Roles determine what actions users can perform:

  • :user – basic access
  • :editor – create/update limited set
  • :admin – full access

Define in lib/tunez/accounts/role.ex:

defmodule Tunez.Accounts.Role do
  use Ash.Type.Enum, values: [:admin, :editor, :user]
end

Reference in user.ex:

attribute :role, Tunez.Accounts.Role do
  allow_nil? false
  default :user
end

Add an action to set roles:

actions do
  defaults [:read]

  update :set_role do
    accept [:role]
  end
end

Writing Policies for Artists

Example for lib/tunez/music/artist.ex:

defmodule Tunez.Music.Artist do
  use Ash.Resource,
      otp_app: :tunez,
      domain: Tunez.Music,
      data_layer: AshPostgres.DataLayer,
      extensions: [AshGraphql.Resource, AshJsonApi.Resource],
      authorizers: [Ash.Policy.Authorizer]

  policies do
    bypass actor_attribute_equals(:role, :admin) do
      authorize_if(always())
    end

    policy action(:create) do
      authorize_if actor_attribute_equals(:role, :admin)
    end

    policy action(:update) do
      authorize_if actor_attribute_equals(:role, :admin)
      authorize_if actor_attribute_equals(:role, :editor)
    end

    policy action(:destroy) do
      authorize_if actor_attribute_equals(:role, :admin)
    end

    policy action_type(:read) do
      authorize_if(always())
    end
  end
end

Use Ash.can?/2 to check actions in templates or LiveView:

Ash.can?({Tunez.Music.Artist, :create}, %{role: :admin})
# => true

Writing Policies for Albums

Track creators and last modifiers:

relationships do
  belongs_to :created_by, Tunez.Accounts.User
  belongs_to :updated_by, Tunez.Accounts.User
end

Actor tracking changes:

changes do
  change relate_actor(:created_by, allow_nil?: true), on: [:create]
  change relate_actor(:updated_by, allow_nil?: true)
end

Define album policies:

policies do
  bypass actor_attribute_equals(:role, :admin) do
    authorize_if(always())
  end

  policy action(:read) do
    authorize_if(always())
  end

  policy action(:create) do
    authorize_if(actor_attribute_equals(:role, :editor))
  end

  policy action(:update) do
    authorize_if(relates_to_actor_via(:created_by))
  end

  policy action_type([:update, :destroy]) do
    authorize_if(expr(^actor(:role) == :editor and created_by_id == ^actor(:id)))
  end
end

Blocking Unauthorized Access in LiveView

defmodule Tunez.Accounts.ForAdminsOnly do
  use TunezWeb, :live_view
  on_mount {TunezWeb.LiveUserAuth, role_required: :admin}
end