GRANDstack Access Control - Basics and Concepts
Tags: graphql, neo4j, grandstack, node
Published: 2020-02-21 06:39:04

Hey there. Thank you for joining me on a journey of exploration and discovery into unlocking some of the most powerful features of the [GRANDstack](https://grandstack.io)! By the end of this series, we'll be able to implement fine-grained discretionary access control features into the GraphQL endpoint generated by [neo4j-graphql-js](https://github.com/neo4j-graphql/neo4j-graphql-js).

Cool, right? I thought so too.

#### Before we dive in...
First off, this series assumes some basic familiarity with GraphQL concepts and the GRANDstack itself (**G**raphQL, **R**eact, **A**pollo, **N**eo4j **D**atabase). Most important of those GRANDstack topics will be its support for complex nested filtering. Luckily, there's a good [blog post](https://blog.grandstack.io/complex-graphql-filtering-with-neo4j-graphql-js-aef19ad06c3e) to get you up to speed.

Second, **this is not a full-fledged tutorial**. . . *yet*. The posts in this series are as much a learning log to document these concepts being developed in real time as they are to invite others to think about and share their own approaches. Learning can be messy. Let's get messy together.

#### And back to the action...

Ok, let's start small. You know what's small? A boring old To-Do app.

(*Wait, you promised an epic journey of awesomeness and are giving me some crappy To-Do app?!?!?*  For now at least, yes.)

We've heard about this thing called the GRANDstack. It has a lot of synergy out of the box. All you really need to get your backend up is your GraphQL type definitions (i.e. the data model). `neo4j-graphql-js` will generate the executable schema from there, which can be served by `apollo-server`.

Ignoring the custom mutation you might use for user login, your type definitions might look like:

```js
const typeDefs = `
type User {
  ID: ID!
  firstName: String
  lastName: String
  email: String!
  todoList: [Task] @relation(name: "TO_DO", direction: "OUT")
}
type Task {
  ID: ID!
  name: String!
  details: String
  location: Point
  complete: Boolean!
  assignedTo: User @relation(name: "TO_DO", direction: "IN")
}
`;
```

Cool beans. We have Users that can be assigned Tasks. Our tasks even take advantage of `neo4j-graphql-js` [Spatial Types](https://grandstack.io/docs/graphql-spatial-types.html) that could be useful in the future!

Let's run it and...

####What went wrong?
Oh, your app works great. That is, if you wanted Bob down the street to see that you need to stop by the pharmacy to pick up some hemorrhoid cream.

We could use the `@additionalLabels` [directive](https://grandstack.io/docs/neo4j-graphql-js-middleware-authorization.html#additionallabels) on `Task` to keep them accessible to only one `User`, but that's kind of limited. What if your mom was going to the pharmacy anyway? Maybe you want *certain* people to be able to see certain tasks.

Maybe you want discretionary access control.

Unfortunately, I am not aware of any clear cut fine-grained access control options for GRANDstack out of the box. If I were, this post would not exist. On the bright side, we get to explore the possibilities together!

#### Filter to the rescue!
I might have mentioned how GRANDstack **does** have out-of-the-box support for complex nested filtering. Could this be the answer we seek? (HINT: I think so.)

Nested filtering means that we can filter the results of any field within our query by the fields of its related types. Those fields of its related types could lead to yet other filterable related types. Ad infinitum.

I don't actually think we need to go on forever. We just need to realize that the  access control list for our business data is itself a graph connected to our primary data model.

We *could* do this with an arbitrarily complex authorization layer, but instead we're going to keep it simple. Let's reduce the access control structure to a single relationship that sits between the `User` and `Task` types. Our updated type definitions might look like:

```js
const typeDefs = `
type User {
  userId: ID!
  firstName: String
  lastName: String
  email: String!
  taskList: [Task] @relation(name: "TO_DO", direction: "OUT")
  visibleTasks: [Task] @relation(name: "CAN_READ", direction: "IN")
}
type Task {
  taskId: ID!
  name: String!
  details: String
  location: Point
  complete: Boolean!
  assignedTo: User @relation(name: "TO_DO", direction: "IN")
  visibleTo: [User] @relation(name: "CAN_READ", direction: "OUT")
}
`;
```

The following filter arguments could then form the basis for locking down our assets:

```gql
query aclTasks($user_id: ID!){
  Task(filter: {visibleTo_some: {userId: $user_id}}) {
    ...task fields
  }
  User {
    taskList(filter: {visibleTo_some: {userId: $user_id}} {
      ...task fields
    }
  }
}
```

If there are other filters that need to be applied, we can wrap them all with an `AND` clause:

```gql
query aclTasks($user_id: ID!){
  Task(filter: {AND: [{visibleTo_some: {userId: $user_id}},
                     {location_distance_lt: {...}}]}) {
    ...task fields
  }
}
```

####Moving ahead in our journey

Oh, I'm sorry. Did I miss something? Your nosy neighbor Bob can still see your pharmaceutical needs can't he because he's savvy enough to submit his own queries without those filters. That dog!

Next time we'll need to figure out how to use a new schema directive to automate the transformation of our GraphQL filter arguments. This will do more to keep Bob out and also keep the queries on the client side a little cleaner. Till then!