Packages
ash_authentication_phoenix
3.0.0-rc.9
3.0.0-rc.9
3.0.0-rc.8
3.0.0-rc.7
3.0.0-rc.6
3.0.0-rc.4
3.0.0-rc.3
3.0.0-rc.2
3.0.0-rc.1
3.0.0-rc.0
2.17.2
2.17.1
2.17.0
2.16.0
2.15.0
2.14.1
2.14.0
2.13.1
2.13.0
2.12.2
2.12.1
2.12.0
2.11.0
2.10.5
2.10.4
2.10.3
2.10.2
2.10.1
2.10.0
2.9.0
2.8.0
2.7.0
2.6.3
2.6.2
2.6.1
2.6.0
2.5.4
2.5.3
2.5.2
2.5.1
2.5.0
2.4.8
2.4.7
2.4.6
2.4.5
2.4.4
2.4.3
2.4.2
2.4.1
2.4.0
2.3.0
2.2.1
2.2.0
2.1.11
2.1.10
2.1.9
2.1.8
2.1.7
2.1.6
2.1.5
2.1.4
2.1.3
2.1.2
2.1.1
2.1.0
2.0.2
2.0.1
2.0.0
2.0.0-rc.2
2.0.0-rc.1
2.0.0-rc.0
1.9.4
1.9.3
1.9.2
1.9.1
1.9.0
1.8.7
1.8.6
1.8.5
1.8.4
1.8.3
1.8.2
1.8.1
1.8.0
1.7.3
1.7.2
1.7.1
1.7.0
1.6.6
1.6.5
1.6.4
1.6.3
1.6.2
1.6.1
1.6.0
1.5.1
1.5.0
1.4.8
1.4.7
1.4.6
1.4.5
1.4.4
1.4.3
1.4.2
1.4.1
1.4.0
1.3.1
1.3.0
1.2.0
1.1.0
1.0.1
1.0.0
Phoenix integration for Ash Authentication
Current section
Files
Jump to
Current section
Files
documentation/topics/scopes.md
<!--
SPDX-FileCopyrightText: 2022 Alembic Pty Ltd
SPDX-License-Identifier: MIT
-->
# Scopes
AshAuthenticationPhoenix can wrap the current actor and tenant in a single
[`Ash.Scope`](https://hexdocs.pm/ash/Ash.Scope.html) struct, so you can pass one
value into your Ash actions rather than threading `actor:` and `tenant:`
separately:
```elixir
# instead of
Ash.read!(query, actor: conn.assigns.current_user, tenant: conn.assigns.current_tenant)
# you can write
Ash.read!(query, scope: conn.assigns.current_user_scope)
```
This mirrors the [`current_scope` concept from `mix phx.gen.auth`](https://hexdocs.pm/phoenix/scopes.html),
while keeping AshAuthentication's support for multiple authenticated resources.
## The scope module
The installer generates a scope struct in your accounts namespace:
```elixir
defmodule MyApp.Accounts.Scope do
defstruct [:actor, :tenant]
defimpl Ash.Scope.ToOpts, for: __MODULE__ do
def get_actor(%{actor: actor}), do: {:ok, actor}
def get_tenant(%{tenant: tenant}), do: {:ok, tenant}
def get_context(_scope), do: :error
def get_tracer(_scope), do: :error
def get_authorize?(_scope), do: :error
end
end
```
This is your extension point. As your application grows, add fields such as the
current organisation, permissions, or locale, and expose them through the
`Ash.Scope.ToOpts` callbacks. The struct is keyed on `:actor` (not `:user`) so a
single struct type serves every authenticated resource.
## Scope assigns
Scopes are built from the assigns that `load_from_session/2` and
`load_from_bearer/2` already set. For each authenticated resource, a
`current_<subject_name>_scope` assign is added alongside the existing
`current_<subject_name>`:
| Subject | Actor assign | Scope assign |
| ------- | ---------------- | ----------------------- |
| `:user` | `:current_user` | `:current_user_scope` |
| `:admin`| `:current_admin` | `:current_admin_scope` |
One subject can also be nominated as the application's default, in which case its
scope is additionally assigned to the singular `:current_scope` — the assign
Phoenix-idiomatic code expects.
## In the plug pipeline
The `set_scope` plug builds the scope and sets the Ash actor. It is a superset of
`set_actor` — use it *instead of* `set_actor`, not alongside it:
```elixir
pipeline :browser do
plug :load_from_session
plug :set_scope, scope: MyApp.Accounts.Scope, default_scope?: true
end
```
* `:scope` — the scope module to instantiate (required).
* `:subject` — the subject to build the scope for. Defaults to `:user`.
* `:default_scope?` — when `true`, also assigns `:current_scope`. Defaults to
`false`.
For a non-default subject:
```elixir
plug :set_scope, scope: MyApp.Accounts.Scope, subject: :admin
```
## In LiveView
Pass a `:scope` option to `ash_authentication_live_session` to add
`current_<subject_name>_scope` assigns to the socket, and `:default_scope` to
nominate the subject whose scope is also assigned to `:current_scope`:
```elixir
ash_authentication_live_session :authenticated_routes,
scope: MyApp.Accounts.Scope,
default_scope: :user do
live "/", MyAppWeb.HomeLive
end
```
```elixir
def mount(_params, _session, socket) do
posts = MyApp.Blog.list_posts!(scope: socket.assigns.current_scope)
{:ok, assign(socket, :posts, posts)}
end
```
## Anonymous requests
When no subject is signed in, the scope is still built with `actor: nil`. Passing
it to an action clears the actor, exactly as `actor: nil` would — so anonymous
requests get a valid (unauthenticated) scope rather than a missing assign.