We can't find the internet
Attempting to reconnect
Something went wrong!
Hang in there while we get back on track
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:
- Policy conditions – determine when a policy applies.
- 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