Structuring for Authorization

Some authorization concerns can be handled by the very structure of the data within our application, and we can use this when we design our schema. The idea is that a single field can authenticate for fields deeper down in our query. After all, a GraphQL document is a tree; if we can have a single field act as a gatekeeper for any data that requires authorization, it could simplify our code and the amount of mental overhead involved in trying to remember what’s public and what isn’t.

A good example of some data in our application that is structured this way is the orders that are associated with a particular customer record. Based on what we’ve done so far, if we’re logged in as a customer and want to get our orders as well as the current menu, we might expect to use a document like this:

 {
  orders {
  id
  items { name quantity}
  }
  menuItems {
  name
  }
 }

We saw how by using the context, we could restrict the values that are returned to only those that belong to the current user, but this produces a small problem. The menuItems field always shows the same thing no matter who is looking at that field, and the orders field always shows different things depending on who is looking at the field, but there’s nothing in the document that might hint that this is what will happen.

The me pattern is an approach where fields that always depend on whoever is viewing the API are placed on some object representing the current user so that the document’s structural hierarchy makes that dependency clear.

Here’s how it might be used in our GraphQL query:

 {
  me {
  orders {
  id
  items { name quantity}
  }
  }
  menuItems {
  name
  }
 }

The menuItems field still happens at the top level because its values are the same regardless of the current user, whereas the orders field has been placed under me. The shape of the document itself helps communicate what is going on.

History of a Pattern

images/aside-icons/info.png

This pattern has its roots in the Relay[28] v1 implementation—the original GraphQL framework, now called “Relay Classic”—where the field was called viewer. It served to provide both authorization and an easy way to ensure that certain data is always loaded in the context of the current user. We’re using the field name me, which is the general convention within the broader GraphQL community at this point.

We’re already in a pretty good place to support this pattern. The :user interface type we created in the AccountTypes module already encapsulates the possibilities of a “current user” in our system, so we can just go ahead and add the requisite field to our schema:

 query ​do
  field ​:me​, ​:user​ ​do
  middleware Middleware.Authorize, ​:any
  resolve &Resolvers.Accounts.me/3
 end
 # Other query fields
 end

The resolver itself couldn’t be simpler; we just need to grab the current user out of the context and return it:

 def​ me(_, _, %{​context:​ %{​current_user:​ current_user}}) ​do
  {​:ok​, current_user}
 end
 def​ me(_, _, _) ​do
  {​:ok​, nil}
 end

We can complete the authorization story by filling out the orders field on the :customer object, which we had previously just stubbed:

 object ​:customer​ ​do
 # Other fields
  field ​:orders​, list_of(​:order​) ​do
» resolve ​fn​ customer, _, _ ->
»import​ Ecto.Query
»
» orders =
» PlateSlate.Ordering.Order
» |> where(​customer_id:​ ^customer.id)
» |> PlateSlate.Repo.all
»
» {​:ok​, orders}
»end
 end
 end

What we end up with is a GraphQL query that looks like this:

 {
  me {
  name
  ... ​on​ Customer { orders { id } }
  }
  menuItems { name }
 }

This tells us a lot. We know that the customer has a name, we know that customers have orders, and we know that those orders are going to be specific to that customer and not include somebody else’s. We also can reasonably expect that the menu items are global values and won’t be different if our friend checks it. In situations where “authorization” boils down to scoping data under other data, it’s often best to express that scope via the GraphQL document itself.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.141.30.210