ash_graphql/documentation/how_to/authorize-with-graphql.md
Riccardo Binetti 513c1ac68f
improvement!: port AshGraphql to Ash 3.0 (#123)
Step 1: update Ash

Step 2: mass rename Api to Domain

Step 3: Ash.Query.expr -> Ash.Expr.expr

Also change ref interpolation

Step 4: remove all warnings

Step 5: remove registries from tests

Step 6: fix filter

Step 7: private? -> !public?

Step 8: Ash.Calculation -> Ash.Resource.Calculation

Step 9: use depend_on_resources/1 -> resources/1

Step 10: add Domain to all resources

Step 11: use Ash module for all actions

Step 12: add public? true all around

Step 13: remove verbose? from options passed during Domain calls

Step 14: add simple_sat

Step 15: Ash.ErrorKind is no more, so remove code from errors

Step 16: sprinkle default_accept :* around tests

Step 17: replace Ash.Changeset.new/2 with Ash.Changeset.for_*

Step 18: calculation fixups

- Context is now a struct and arguments go under the arguments key
- Function based calculations receive a list of records
- Add a select to query-based loads
- select -> load

Step 19: pass the correct name to pass the policy in tests

Step 20: Ash.Query.new/2 is no more

Step 21: add AshGraphql.Resource.embedded? utility function

Use that instead of Ash.Type.embedded_type?(resource_or_type) since resources
are not types anymore

Step 22: handle struct + instance_of: Resource in unions

Resources are not type anymore so they need to be passed this way in unions

Step 23: ensure we only check GraphQL actions for pagination

All reads are now paginated by default, so this triggered a compilation error

Step 24: swap arguments for sort on calculations

Step 25: remove unused debug? option
2024-04-01 14:03:06 -04:00

2.6 KiB

Authorize with GraphQL

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 via changeset.context and query.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
end

If you are doing authorization, you'll need to provide an actor.

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
end

Policy 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").