AshGraphql uses three special keys in the absinthe context:
:actor- the current actor, to be used for authorization/preparations/changes:tenant- a tenant when using multitenancy.:ash_context- a map of arbitrary context to be passed into the changeset/query. Accessible viachangeset.contextandquery.context
By default, authorize? in the domain is set to true. To disable authorization for a given domain in graphql, use:
graphql do
authorize? false
endIf you are doing authorization, you'll need to provide an actor.
Using AshAuthentication
If you have not yet installed AshAuthentication, you can install it with igniter:
# installs ash_authentication & ash_authentication_phoenix
mix igniter.install ash_authentication_phoenix
If you've already set up AshGraphql before adding AshAuthentication, you will
just need to make sure that your :graphql scope in your router looks like this:
pipeline :graphql do
plug :load_from_bearer
plug :set_actor, :user
plug AshGraphql.Plug
endUsing Something Else
To set the actor for authorization, you'll need to add an actor key to the
absinthe context. Typically, you would have a plug that fetches the current user and uses Ash.PlugHelpers.set_actor/2 to set the actor in the conn (likewise with Ash.PlugHelpers.set_tenant/2).
Just add AshGraphql.Plug somewhere after that in the pipeline and the your
GraphQL APIs will have the correct authorization.
defmodule MyAppWeb.Router do
pipeline :api do
# ...
plug :get_actor_from_token
plug AshGraphql.Plug
end
scope "/" do
forward "/gql", Absinthe.Plug, schema: YourSchema
forward "/playground",
Absinthe.Plug.GraphiQL,
schema: YourSchema,
interface: :playground
end
def get_actor_from_token(conn, _opts) do
with ["" <> token] <- get_req_header(conn, "authorization"),
{:ok, user, _claims} <- MyApp.Guardian.resource_from_token(token) do
conn
|> set_actor(user)
else
_ -> conn
end
end
endPolicy Breakdowns
By default, unauthorized requests simply return forbidden in the message. If you prefer to show policy breakdowns in your GraphQL errors, you can set the config option:
config :ash_graphql, :policies, show_policy_breakdowns?: true{
"data": {
"attendanceRecords": null
},
"errors": [
{
"code": "forbidden",
"fields": [],
"locations": [
{
"column": 3,
"line": 2
}
],
"message": "MyApp.Authentication.User.read\n\n\n\n\nPolicy Breakdown\n Policy | ⛔:\n forbid unless: actor is active | ✓ | ⬇ \n authorize if: actor is Executive | ✘ | ⬇",
"path": ["attendanceRecords"],
"short_message": "forbidden",
"vars": {}
}
]
}Be careful, as this can be an attack vector in some systems (i.e "here is exactly what you need to make true to do what you want to do").
Field Policies
By default, field policies in AshGraphql work by producing a null value for any forbidden field, as well as an error in the errors list.
nullability
Any fields with field policies on them should be nullable. If they are not nullable, the parent object will also be null (and considered in an error state), because null is not a valid type for that field.
To make specific fields nullable even if they are not nullable by definition, use the nullable_fields option.
graphql do
type :post
nullable_fields [:foo, :bar, :baz]
endTo automatically make fields that may be hidden by authorization nullable, use forbidden_field_mode :nullable.
Built-in Ash field policies are detected automatically, excluding catch-all policies like authorize_if always(). Custom authorizers can participate by reporting the fields they may hide.
graphql do
type :post
forbidden_field_mode :nullable
endTo expose forbidden fields as data instead of GraphQL errors, use forbidden_field_mode :materialized.
Fields that may be hidden by authorization are exposed as unions whose members are a field-specific value wrapper and ForbiddenField.
Singular relationships with allow_forbidden_field? true are exposed as unions whose members are the destination type and ForbiddenField.
graphql do
type :post
forbidden_field_mode :materialized
end
relationships do
belongs_to :organization, MyApp.Organization do
public? true
allow_forbidden_field? true
end
endRelationships
Field policies cover attributes, calculations, and aggregates. They do not currently target relationships.
Singular relationships can still be materialized when Ash may return a forbidden relationship sentinel. To opt into that behavior, configure the relationship with allow_forbidden_field? true and use forbidden_field_mode :materialized.
graphql do
type :post
forbidden_field_mode :materialized
end
relationships do
belongs_to :organization, MyApp.Organization do
public? true
allow_nil? false
allow_forbidden_field? true
end
endThis exposes the singular relationship as a union of the destination type and ForbiddenField:
type Post {
organization: PostOrganizationRelationship!
}
union PostOrganizationRelationship = Organization | ForbiddenFieldThis only applies to singular relationships. List, paginated, and Relay connection relationships are not materialized as forbidden unions.