Graphql Absinthe Elixir permission based accessible fields

What is the proper way to define fields that may not be accessible to all users.

For example, a general user can query the users and find out another users handle, but only admin users can find out their email address. The user type defines it as a field but it may not be accessible. Should there be a separate type for what a general user can see? How would you define it?

Sorry if that isn’t that clear I just don’t possess the vocabulary.

Edit: Caution: Graphql documentation disagrees with this approach. Use with caution. Wherever you need a private field you must include the appropriate middlewares.

Use absinthe middleware.

Here is some code how to do it. In this example the authenticated user can see the email addresses. The anonymous user can’t. You can adjust the logic to require whatever permissions you want.

defmodule MySite.Middleware.RequireAuthenticated do
  @behaviour Absinthe.Middleware

  @moduledoc """
  Middleware to require authenticated user
  """

  def call(resolution, config) do
    case resolution.context do
      %{current_user: _} ->
        resolution
      _ ->
        Absinthe.Resolution.put_result(resolution, {:error, "unauthenticated"})
    end
  end
end

and then you define your object:

  object :user do
    field :id, :id
    field :username, :string 
    field :email, :string do
      middleware MySite.Middleware.RequireAuthenticated
      middleware Absinthe.Middleware.MapGet, :email
    end
  end

So our field email is protected by the RequireAuthenticated middleware. But according to the link above

One use of middleware/3 is setting the default middleware on a field,
replacing the default_resolver macro.

This happens also by using the middleware/2 macro on the field. This is why we need to also add

  middleware Absinthe.Middleware.MapGet, :email

to the list of middlewares on the field.

Finally when we perform a query

query {
  user(id: 1){
    username
    email
    id
  }
}

We get the response with the open fields filled and the protected fields nullified

{
  "errors": [
    {
      "message": "In field "email": unauthenticated",
      "locations": [
        {
          "line": 4,
          "column": 0
        }
      ]
    }
  ],
  "data": {
    "user": {
      "username": "MyAwesomeUsername",
      "id": "1",
      "email": null
    }
  }
}

You can also use the middleware/3 callback so your object don’t get too verbose

  def middleware(middleware, %{identifier: :email} = field, _object) do
    [MySite.Middleware.RequireAuthenticated] ++
      [{Absinthe.Middleware.MapGet, :email}] ++
      middleware
  end

With a bit of creative use of the __using__/1 callback you can get a bunch of such functions out of your main schema file.

Read more here: Source link