Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Lists and pagination authorization in GraphQL business layer

In Dan Schafer's excellent "GraphQL at Facebook" talk from React Europe he goes over how centralizing authorization in business layer models avoids the problem of having to duplicate authorization logic for every edge that leads to an authorized node.

Three layer

This works fine for something like Todo.getById(1) which in my case eventually ends up querying a database for SELECT * from todos WHERE id=1 and then verifying authorization with checkCanSee(resultFromDatabase).

However, let's say my todos table now contains 100,000 todos from multiple users, performing authorization purely in the business layer becomes impractical as I'd need to fetch every todo, filter the result using the shared authorization logic and then slicing that to perform pagination.

Am I wrong thinking that the only way to solve this is by letting authorization logic reside in the persistence layer itself?

like image 413
Simen Brekken Avatar asked Aug 31 '17 06:08

Simen Brekken


2 Answers

I think one of the takeaways from Dan’s talk is the difference in how authorization is handled with GraphQL, as opposed to a typical REST endpoint.

In REST, each resource is typically associated with a single endpoint. When a request is made to that endpoint, it makes sense to check whether the requestor is authorized before processing the request. With GraphQL we may be fetching multiple resources within the same request, so this behavior is no longer desirable. As Dan puts it:

We don’t want to completely blow up the request if you can’t see one of [the requested resources].

So the preferred approach with GraphQL is to implement some kind of per-node mechanism for authorization, and to only return the resources the requester is authorized to see. And that is exactly what the example in the talk shows – one way of doing that.

If you store your to-dos in a SQL database table, it would make perfect sense for your code to just make a query like SELECT * from todos WHERE creator_id=${viewer.id} and omit using a function like checkCanSee altogether.

Similarly, you can bake pagination right into your query with limit-offset, cursors, etc. And yes, since you’re now letting your DB do the heavy lifting, you could say that we’ve moved into the persistence layer. However, it’s still up to your business logic to take the request, sanitize the inputs, construct an appropriate query and return the results in a form GraphQL can use.

I can’t speak for Dan, but I imagine his intent was not to suggest this was the only (or even optimal) way to implement authorization for a node. I think the bigger point is that if you are, for example, fetching:

{
  header
  todos  {
    description
  }
  quoteOfTheDay
}

even an unauthorized client should still get a response back from the server that it can then use to render a page for the end-user (even if that response includes an empty array of to-dos).

like image 167
Daniel Rearden Avatar answered Dec 06 '22 07:12

Daniel Rearden


After another round of searching for answers I stumbled upon a few interesting comments from Lee Byron that shed some light on the subject:

  • Let's talk about caching in GraphQL
  • Over fetching data on the server

The storage layer shouldn't deal with authorization, but it should expose APIs that limit the amount of data that's being returned.

In the above scenario dealing with hundreds of thousands of todos this could be implemented by exposing something like getTodosByUserId which returns all todos beloning to a particular user. The business logic layer would then deal with both authorization and pagination by filtering a slicing the result. But what if a user has thousands of todos? One would probably add another filter option to the storage layer such as getTodosByUserId({ userId: 1, completed: false }).

An important take-away from the "Let's talk about caching" comment by Lee is that results from the storage layer should be easily cachable in something like Redis or memcached. Fetching a thousand todos from Redis and then filtering and slicing them in your business logic layer is probably a lot less expensive than having to query an SQL database for every request.

like image 28
Simen Brekken Avatar answered Dec 06 '22 06:12

Simen Brekken