Good day, all.
Allow me to navigate you on a journey through Authentication and Authorization in the land of Neo4j GraphQL.
Bind your acquired knowledge of our GraphQL directive to
Type Definitions, where applied, enables sophisticated auth patterns.
This blog is the written format of my NODES 2021 talk, which you can also watch if you rather do that:
What is Neo4j GraphQL?
Find out more about the Neo4j GraphQL library here
Auth in GraphQL
In GraphQL there isn’t one way of going about auth, and it’s highly dependent on your business requirements and needs. Due to GraphQL being a newer technology, and highly different from REST, lots of patterns need to either be replicated or repeated. There are, however, some really cool GraphQL tools and patterns for auth — though, many can be cumbersome and slow. Most either lead to surplus database calls or extra resolver invocations.
Due to the fact that Neo4j GraphQL aims to produce a single Cypher query from a given GraphQL operation, we wanted to roll our own auth solution so that we could embed rules into the Cypher query. Rolling our own auth means it’s easier for you, as a developer, to get started, plus it increases performance due to the reduction in database calls.
Auth in Neo4j GraphQL
The term “auth” is encapsulating both authentication and authorization. For the most part, you will use our GraphQL directive for authorization, and for authentication, we suggest using the OGM, which you will learn about shortly.
Throughout this blog we will use the following data model:
That can be expressed with the following GraphQL type definitions:
The model is arbitrary and I will add and remove fields to this as and when needed.
Authentication in Neo4j GraphQL
JSON Web Tokens
The Neo4j GraphQL library uses JSON web tokens. Below is a JSON representation of an arbitrary token:
Typically you will see the JWT attached to the authorizationheader of an HTTP request:
In the example above, the JWT has been encoded. https://jwt.io/ is a great tool for playing around with JWT’s.
When setting up your Neo4j GraphQL server you should dependency inject the incoming request into the context so we can pick up on the headers:
req here allows the Neo4j GraphQL directive to pick up on the authorization header.
Here we will use the Neo4j GraphQL OGM to authenticate users using a custom resolver:
The OGM is a wrapper around the Neo4j GraphQL API. You use the OGM by supplying some GraphQL Type Definitions, the same ones as your API, and out comes a class instance with lots of methods. Each method you invoke will call the relevant GraphQL query or mutation, and thus when invoked, Cypher will be produced and executed.
Below is an example of setting up the Neo4j GraphQL OGM:
Above, you can see we invoke the
.model() method on the OGM. For each type in your definitions, you can receive a model. In our schema we would have access to all three:
Post models. Using a model, you can call the following methods:
Each method will call the GraphQL API and fundamentally interact with your Neo4j database.
With this knowledge of the OGM, below we will use it to sign in users. Here I define a custom mutation
signIn in our GraphQL schema:
Then I create a function called
signIn and pass it into the Neo4j GraphQL library as a resolver:
Finally, now users of your API can invoke that mutation by performing:
The returned value would be the JWT. With this token, you should append it to the header of the request. Here is a simplified fetch example of appending this header:
When in development, you can append this JWT in the headers section of GraphQL playground:
This was an example of using the .
find() method on a Model. You could, however, use the OGM for a signup method where you use the .
Authorization in Neo4j GraphQL
Now that we know how to use the OGM to sign in users and return a JWT, in this section we can dive deeper into the auth directive. However, just before we dive deep into authorization let’s use a simple example to ease our way into this. In our application, we want to make sure that only signed-in users can create a post:
Here is your first introduction to the auth directive. The directive can be used on Type definitions, as seen above, and or on fields. Each auth directive must have an array of rules where each rule is evaluated inside a logical OR. The
isAuthenticated key is enforcing that there is a valid JWT in the request headers.
In the first example, above, when using the auth directive you may have noticed the usage of the
operations key. It does what it says on the tin and only applies the rule on that operation. The full list of
operations are shown in the example below:
Often you will want to specify all the
operations, and so if you don’t specify any
operations all of them will be applied:
It’s worth noting that by default your APIs are insecure from all angles. When deploying to production you should ensure that each and every operation is covered.
Automatically append predicates
Briefly, we have seen how to use the auth directive to ensure that authenticated users can create a post. However, what about finer-grained auth rules? In the example below, we have Bob and Jane both creating many posts. The catch is that each post is private and hypothetically there are no public posts:
Given this example, we would like to ensure that Bob can’t interact with any of Jane’s posts and that Jane has the same restrictions on Bob’s posts.
First, notice how we have used extend here. Using
extend is helpful when dealing with larger schemas and auth rules. For the rest of this blog, I will use
You see that we have used the
where key. This key is used to specify an equality predicate between the creator’s id and the JWT
sub property. Normally without the where rule, applied users could query the API for users:
This would produce the Cypher statement:
However, with the where rule applied, and if applicable, the generated Cypher would be:
where will get you off to a great start, although it’s rather restrictive and would be hard to enable other interactions by anyone other than the creator themself. Read on to learn more about
roles where applied will enable you to express even finer-grained rules.
Enable specific users to perform specific actions.
For most use cases you will find yourself using
allow. It enforces things! Here we have Jane, who is a troll, commenting on Bob’s post. How can we allow Bob to delete Jane’s comment?
First, let’s look at how we can enable Bob to delete his own comments:
Allow, in Cypher, will add a validate check just after matching a node. For this example, inside the check is a predicate that ensures the comment creator id is equal to the JWT.sub property, and if not throws out of the transaction.
Now we have enabled Bob to delete his own comments, let’s use OR logic to enable the post creator too:
Notice the nested traversal of the post creator.
Enforce equality between a JWT property and the target node property.
bind can be used in tandem with
allow, and some may ask what’s the difference?
allow enables the ability to perform an action, whereas
bind enforces that patterns, on upsert, exist. Below is an example where we would want to enforce a pattern. Bob has posted for Jane, and this is something we don’t want:
We can use
bind on create operations so that when Bob tries to create a bog for Jane the transaction fails:
As well as
bind uses a validate check, however bind validation is done after the operation.
Use to add RBAC (role based access control) to your schema.
This is the last key we‘re’ going to dive into. You may have roles in your JWT claims, and you want to enable someone with a particular role to do something. Here we have a hypothetical
recoveryCode on our user node, and not only are we putting auth on the field, but using OR logic to enable either the user themself or an admin:
Not only have we dived into all of the keys in the auth directive, but we also learned how to authenticate using our understanding of the OGM. The techniques used in this blog will enable you to build production-ready Neo4j GraphQL APIs. What are you waiting for?
Learn More About Neo4j GraphQL
Neo4j GraphQL Library is available today via npm.