usage-rules/authorization.md
authorize?: falseactor option# Good
Post
|> Ash.Query.for_read(:read, %{}, actor: current_user)
|> Ash.read!()
# BAD, DO NOT DO THIS
Post
|> Ash.Query.for_read(:read, %{})
|> Ash.read!(actor: current_user)
To use policies, add the Ash.Policy.Authorizer to your resource:
defmodule MyApp.Post do
use Ash.Resource,
domain: MyApp.Blog,
authorizers: [Ash.Policy.Authorizer]
# Rest of resource definition...
end
Policies determine what actions on a resource are permitted for a given actor. Define policies in the policies block:
policies do
# A simple policy that applies to all read actions
policy action_type(:read) do
# Authorize if record is public
authorize_if expr(public == true)
# Authorize if actor is the owner
authorize_if relates_to_actor_via(:owner)
end
# A policy for create actions
policy action_type(:create) do
# Only allow active users to create records
forbid_unless actor_attribute_equals(:active, true)
# Ensure the record being created relates to the actor
authorize_if relating_to_actor(:owner)
end
end
Policies evaluate from top to bottom with the following logic:
the first check that yields a result determines the policy outcome
# WRONG - This is OR logic, not AND logic!
policy action_type(:update) do
authorize_if actor_attribute_equals(:admin?, true) # If this passes, policy passes
authorize_if relates_to_actor_via(:owner) # Only checked if first fails
end
To require BOTH conditions in that example, you would use forbid_unless for the first condition:
# CORRECT - This requires BOTH conditions
policy action_type(:update) do
forbid_unless actor_attribute_equals(:admin?, true) # Must be admin
authorize_if relates_to_actor_via(:owner) # AND must be owner
end
Alternative patterns for AND logic:
expr(condition1 and condition2)forbid_unless for required conditions, then authorize_if for the final checkUse bypass policies to allow certain actors to bypass other policy restrictions. This should be used almost exclusively for admin bypasses.
policies do
# Bypass policy for admins - if this passes, other policies don't need to pass
bypass actor_attribute_equals(:admin, true) do
authorize_if always()
end
# Regular policies follow...
policy action_type(:read) do
# ...
end
end
Field policies control access to specific fields (attributes, calculations, aggregates):
field_policies do
# Only supervisors can see the salary field
field_policy :salary do
authorize_if actor_attribute_equals(:role, :supervisor)
end
# Allow access to all other fields
field_policy :* do
authorize_if always()
end
end
There are two main types of checks used in policies:
You can use built-in checks or create custom ones:
# Built-in checks
authorize_if actor_attribute_equals(:role, :admin)
authorize_if relates_to_actor_via(:owner)
authorize_if expr(public == true)
# Custom check module
authorize_if MyApp.Checks.ActorHasPermission
Create custom checks by implementing Ash.Policy.SimpleCheck or Ash.Policy.FilterCheck:
# Simple check - returns true/false
defmodule MyApp.Checks.ActorHasRole do
use Ash.Policy.SimpleCheck
def match?(%{role: actor_role}, _context, opts) do
actor_role == (opts[:role] || :admin)
end
def match?(_, _, _), do: false
end
# Filter check - returns query filter
defmodule MyApp.Checks.VisibleToUserLevel do
use Ash.Policy.FilterCheck
def filter(actor, _authorizer, _opts) do
expr(visibility_level <= ^actor.user_level)
end
end
# Usage
policy action_type(:read) do
authorize_if {MyApp.Checks.ActorHasRole, role: :manager}
authorize_if MyApp.Checks.VisibleToUserLevel
end