If your resources use Ash.Policy.Authorizer, AshScim's internal Ash
calls (the reads, creates, updates, destroys, and loads it issues from
the router) need a way to bypass your application-level policies.
Otherwise filters that work fine in your app code can fail when issued
from the SCIM router because of public?:, field policies, or
restrictive read policies.
This mirrors AshAuthentication's
AshAuthenticationInteraction
check. AshScim provides
AshScim.Checks.AshScimInteraction,
which returns true whenever the changeset/query was built by AshScim
itself.
To enable the bypass on a SCIM-exposed resource, add it as the first policy:
defmodule MyApp.Accounts.User do
use Ash.Resource,
# ...
authorizers: [Ash.Policy.Authorizer],
extensions: [AshAuthentication, AshScim.User]
policies do
bypass AshAuthentication.Checks.AshAuthenticationInteraction do
authorize_if always()
end
bypass AshScim.Checks.AshScimInteraction do
authorize_if always()
end
# ... your normal user-facing policies ...
end
# ...
endEvery changeset and query the router constructs is tagged with the
private context flag %{private: %{ash_scim?: true}}. The check
AshScim.Checks.AshScimInteraction matches on that flag and returns
true, so the bypass authorizes the operation.
Crucially, this flag can only be set by code running inside the
AshScim router. Your application code can't accidentally trip it: an
end-user request that ends up calling Ash.read on the same resource
goes through your normal policies.
The same flag is also passed via :context on Ash.get, Ash.load, and
Ash.bulk_destroy calls so policy checks see it consistently across all
the entry points the router uses.
The pattern above is convenient but blunt — it gives the router
unrestricted access to the resource. If you want to narrow it (e.g.
"the SCIM router can only read users with active == true"), replace
the bypass with a regular policy that checks the SCIM context flag plus
additional conditions:
policies do
# SCIM has limited access: it can read/update users, but only active ones.
policy AshScim.Checks.AshScimInteraction do
authorize_if action_type([:read, :update])
authorize_if expr(active == true)
end
# ... regular user-facing policies ...
endThe check is just an Ash.Policy.SimpleCheck — compose it however your
authorization model needs.
You could in principle skip the bypass and use a service-account user with policies that grant full SCIM access. Two reasons we recommend the bypass instead:
- It's more explicit. Reading the resource's
policiesblock, you can immediately see the SCIM-managed paths in or out. - It survives actor changes. If your auth implementation ever
changes (e.g. you switch from
AshScim.Auth.AshAuthenticationTokento a per-IdP service identity), you don't have to re-encode "this thing can do SCIM stuff" in your policies — the context flag carries that intent regardless of who the actor is.