Claude Code Plugins

Community-maintained marketplace

Feedback

Authorization with policies, bypasses, and custom checks

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

name ash-authorization
description Authorization with policies, bypasses, and custom checks

Authorization

  • When performing administrative actions, you can bypass authorization with authorize?: false
  • To run actions as a particular user, look that user up and pass it as the actor option
  • Always set the actor on the query/changeset/input, not when calling the action
  • Use policies to define authorization rules
# 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)

Policies

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

Policy Basics

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

Policy Evaluation Flow

Policies evaluate from top to bottom with the following logic:

  1. All policies that apply to an action must pass for the action to be allowed
  2. Within each policy, checks evaluate from top to bottom
  3. The first check that produces a decision determines the policy result
  4. If no check produces a decision, the policy defaults to forbidden

IMPORTANT: Policy Check 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:

  • Use multiple separate policies (each must pass independently)
  • Use a single complex expression with expr(condition1 and condition2)
  • Use forbid_unless for required conditions, then authorize_if for the final check

Bypass Policies

Use 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

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

Policy Checks

There are two main types of checks used in policies:

  1. Simple checks - Return true/false answers (e.g., "is the actor an admin?")
  2. Filter checks - Return filters to apply to data (e.g., "only show records owned by the actor")

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

Custom Policy Checks

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