View Source Authorize Access to Resources
Mix.install(
  [
    {:ash, "~> 3.0"},
    {:simple_sat, "~> 0.1"},
    {:kino, "~> 0.12"}
  ],
  consolidate_protocols: false
)
Logger.configure(level: :warning)
Application.put_env(:ash, :policies, show_policy_breakdowns?: true)Introduction
A key feature of Ash is the ability to build security directly into your resources. We do this with policies.
Because how you write policies is extremely situational, this how-to guide provides a list of "considerations" as opposed to "instructions".
For more context, read the policies guide.
Writing Policies
- Consider whether or not you want to adopt a specific style of authorization, like ACL, or RBAC. For standard RBAC, look into AshRbac, and you may not need to write any of your own policies at that point
 - Determine if there are any 
bypasspolicies to add (admin users, super users, etc.). Consider placing this on the domain, instead of the resource - Begin by making an inventory of each action on your resource, and under what conditions a given actor may be allowed to perform them. If all actions of a given type have the same criteria, we will typically use the 
action_type(:type)condition - Armed with this inventory, begin to write policies. Start simple, write a policy per action type, and add a description of what your policy accomplishes.
 - Find patterns, like cross-cutting checks that exist in all policies, that can be expressed as smaller, simpler policies
 - Determine if any field policies are required to prohibit access to attributes/calculations/aggregates
 - Finally, you can confirm your understanding of the authorization flow for a given resource by generating policy charts with 
mix ash.generate_policy_charts(field policies are not currently included in the generated charts) 
Example
defmodule User do
  use Ash.Resource,
    domain: Domain,
    data_layer: Ash.DataLayer.Ets
  actions do
    defaults [:read, create: [:admin?]]
  end
  attributes do
    uuid_primary_key :id
    attribute :admin?, :boolean do
      allow_nil? false
      default false
    end
  end
end
defmodule Tweet do
  use Ash.Resource,
    domain: Domain,
    data_layer: Ash.DataLayer.Ets,
    authorizers: [Ash.Policy.Authorizer]
  attributes do
    uuid_primary_key :id
    attribute :text, :string do
      allow_nil? false
      constraints max_length: 144
      public? true
    end
    attribute :hidden?, :boolean do
      allow_nil? false
      default false
      public? true
    end
    attribute :private_note, :string do
      sensitive? true
      public? true
    end
  end
  calculations do
    calculate :tweet_length, :integer, expr(string_length(text)) do
      public? true
    end
  end
  relationships do
    belongs_to :user, User, allow_nil?: false
  end
  actions do
    defaults [:read, update: [:text, :hidden?, :private_note]]
    create :create do
      primary? true
      accept [:text, :hidden?, :private_note]
      change relate_actor(:user)
    end
  end
  policies do
    # action_type-based policies
    policy action_type(:read) do
      # each policy has a description
      description "If a tweet is hidden, only the author can read it. Otherwise, anyone can."
      # first check this. If true, then this policy passes
      authorize_if relates_to_actor_via(:user)
      # then check this. If false, then this policy fails
      forbid_if expr(hidden? == true)
      # otherwise, this policy passes
      authorize_if always()
    end
    # blanket allow-listing of creates
    policy action_type(:create) do
      description "Anyone can create a tweet"
      authorize_if always()
    end
    policy action_type(:update) do
      description "Only an admin or the user who tweeted can edit their tweet"
      # first check this. If true, then this policy passes
      authorize_if actor_attribute_equals(:admin?, true)
      # then check this. If true, then this policy passes
      authorize_if relates_to_actor_via(:user)
      # otherwise, there is nothing left to check and no decision, so *this policy fails*
    end
  end
  field_policies do
    # anyone can see these fields
    field_policy [:text, :tweet_length] do
      description "Public tweet fields are visible"
      authorize_if always()
    end
    field_policy [:hidden?, :private_note] do
      description "hidden? and private_note are only visible to the author"
      authorize_if relates_to_actor_via(:user)
    end
  end
end
defmodule Domain do
  use Ash.Domain,
    validate_config_inclusion?: false
  resources do
    resource Tweet do
      define :create_tweet, action: :create, args: [:text]
      define :update_tweet, action: :update, args: [:text]
      define :list_tweets, action: :read
      define :get_tweet, action: :read, get_by: [:id]
    end
    resource User do
      define :create_user, action: :create
    end
  end
end
{:module, Domain, <<70, 79, 82, 49, 0, 2, 117, ...>>,
 [
   Ash.Domain.Dsl.Resources.Resource,
   Ash.Domain.Dsl.Resources.Options,
   Ash.Domain.Dsl,
   %{opts: [], entities: [...]},
   Ash.Domain.Dsl,
   Ash.Domain.Dsl.Resources.Options,
   ...
 ]}Interacting with resources that have policies
# doing forbidden things produces an `Ash.Error.Forbidden`
user = Domain.create_user!()
other_user = Domain.create_user!()
tweet = Domain.create_tweet!("hello world!", actor: user)
Domain.update_tweet!(tweet, "Goodbye world", actor: other_user)# Reading data applies policies as filters
user = Domain.create_user!()
other_user = Domain.create_user!()
my_hidden_tweet = Domain.create_tweet!("hello world!", %{hidden?: true}, actor: user)
other_users_hidden_tweet =
  Domain.create_tweet!("hello world!", %{hidden?: true}, actor: other_user)
my_tweet = Domain.create_tweet!("hello world!", actor: user)
other_users_tweet = Domain.create_tweet!("hello world!", actor: other_user)
tweet_ids = Domain.list_tweets!(actor: user) |> Enum.map(& &1.id)
# I see my own hidden tweets, and other users non-hidden tweets
true = my_hidden_tweet.id in tweet_ids
true = other_users_tweet.id in tweet_ids
# but not other users hidden tweets
false = other_users_hidden_tweet.id in tweet_ids
:ok:ok# Field policies return hidden fields as `%Ash.ForbiddenField{}`
user = Domain.create_user!()
other_user = Domain.create_user!()
other_users_tweet =
  Domain.create_tweet!("hello world!", %{private_note: "you can't see this!"}, actor: other_user)
%Ash.ForbiddenField{} = Domain.get_tweet!(other_users_tweet.id, actor: user).private_note#Ash.ForbiddenField<field: :private_note, type: :attribute, ...>Tweet
|> Ash.Policy.Chart.Mermaid.chart()
|> Kino.Shorts.mermaid()flowchart TB
subgraph at least one policy applies
direction TB
at_least_one_policy["action.type == :read
or action.type == :create
or action.type == :update"]
end
at_least_one_policy--False-->Forbidden
at_least_one_policy--True-->0_conditions
subgraph Policy 1[If a tweet is hidden, only the author can read it. Otherwise, anyone can.]
direction TB
0_conditions{"action.type == :read"}
0_checks_0{"record.user == actor"}
0_checks_1{"hidden? == true"}
end
0_conditions--True-->0_checks_0
0_conditions--False-->1_conditions
0_checks_0--True-->1_conditions
0_checks_0--False-->0_checks_1
0_checks_1--True-->Forbidden
0_checks_1--False-->1_conditions
subgraph Policy 2[Anyone can create a tweet]
direction TB
1_conditions{"action.type == :create"}
end
subgraph Policy 3[Only an admin or the user who tweeted can edit their tweet]
direction TB
2_conditions{"action.type == :update"}
2_checks_0{"actor.admin? == true"}
2_checks_1{"record.user == actor"}
end
2_conditions--True-->2_checks_0
2_conditions--False-->Authorized
2_checks_0--True-->Authorized
2_checks_0--False-->2_checks_1
2_checks_1--True-->Authorized
2_checks_1--False-->Forbidden
subgraph results[Results]
Authorized([Authorized])
Forbidden([Forbidden])
end
1_conditions--Or-->2_conditions