Authorization
|
This is the documentation of the GraphQL Library version 7. For the long-term support (LTS) version 5, refer to GraphQL Library version 5 LTS. |
Authorization rules cover what specific data a generated Cypher query is allowed to access. They use predicates to evaluate the data accessed by the Cypher generated from a GraphQL query, thus allowing or disallowing execution within the context of nodes and their properties.
All authorization rules have an implied requirement for authentication, given that the rules are normally evaluated against values in the JWT payload.
In the case of explicit authentication, configured using the @authentication directive, it is only evaluated during Cypher translation time.
Unauthenticated requests with queries requiring authentication never reach the database.
|
The |
Filtering rules
Filtering rules filter out data which users do not have access to, without throwing any errors. These rules are translated into filtering predicates, which are evaluated against matched data in the database.
Filters for such rules work just like filters for queries and aggregations. Refer to Filtering.
Filtering rules protect data as well as obfuscate the information on the existence of that data to unauthorized users.
For instance, here is how to filter out Post nodes which don’t belong to the current User:
type User @node {
id: ID!
}
type Post @node @authorization(filter: [
{ where: { node: { authors: { some: { id: { eq: "$jwt.sub" } } } } } }
]) {
title: String!
content: String!
authors: [User!]! @relationship(type: "AUTHORED", direction: IN)
}
Operations
Filtering can be configured to only be performed on certain operations:
-
READ -
AGGREGATE -
UPDATE -
DELETE -
CREATE_RELATIONSHIP -
DELETE_RELATIONSHIP
For instance, to only require filtering for the reading and aggregating posts:
type Post @node @authorization(filter: [
{ operations: [READ, AGGREGATE], where: { node: { author: { id: { eq: "$jwt.sub" } } } } }
]) {
title: String!
content: String!
authors: [User!]! @relationship(type: "AUTHORED", direction: IN)
}
|
In case there is no |
Validating rules
Validating rules throw an error if a query is executed against data which users do not have access to.
These rules are evaluated in the database via filtering predicates containing calls to
apoc.util.validatePredicate.
For instance, here is how to throw an error if a User is accessed by anyone but the user themselves or an admin:
type JWT @jwt {
roles: [String!]!
}
type User @node @authorization(validate: [
{ where: { node: { id: { eq: "$jwt.sub" } } } }
{ where: { jwt: { roles: { includes: "admin" } } } }
]) {
id: ID!
}
Operations
Validation can be configured to only be performed on certain operations:
-
READ -
AGGREGATE -
CREATE -
UPDATE -
DELETE -
CREATE_RELATIONSHIP -
DELETE_RELATIONSHIP
For instance, to only require validation for the update or deletion of a post:
type Post @node @authorization(validate: [
{
operations: [UPDATE, DELETE]
where: { node: { authors: { some: { id: { eq: "$jwt.sub" } } } } }
}
]) {
title: String!
content: String!
authors: [User!]! @relationship(type: "AUTHORED", direction: IN)
}
|
In case there is no |
When
Validation can be configured to only be performed before or after an operation is executed.
This is done using the when argument which accepts an array of the following values:
-
BEFORE -
AFTER
Additionally, some operations only support validation either before or after them, which is summarised in this table:
operation |
when |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
As an example, let’s say you want someone to be able to update a post. If you want to check that after the update the author of the post is still the current user, do the following:
type Post @node @authorization(validate: [
{ operations: [UPDATE], when: [AFTER], where: { node: { author: { id: { eq: "$jwt.sub" } } } } }
]) {
title: String!
content: String!
author: User @relationship(type: "AUTHORED", direction: IN)
}
A note on CREATE_RELATIONSHIP and DELETE_RELATIONSHIP
Creating or deleting a relationship affects both ends of the relationship.
This means that the CREATE_RELATIONSHIP and DELETE_RELATIONSHIP validation rules apply to both the source and the target node.
Note that for CREATE_RELATIONSHIP only the AFTER validation applies for the source node.
For example, consider a Post type and a User type both with CREATE_RELATIONSHIP validation rules for BEFORE and AFTER:
* Only an existing author of the post can add another co-author.
* Only a user which is not locked can be added as an author to a post.
*
The rules for each type can be combined into one but we are separating them here for clarity:
type Post @node @authorization(validate: [
{ operations: [CREATE_RELATIONSHIP], when: [BEFORE], where: { node: { authors: { some: { id: { eq: "$jwt.sub" } } } } } } (1)
{ operations: [CREATE_RELATIONSHIP], when: [AFTER], where: { node: { authors: { some: { id: { eq: "$jwt.sub" } } } } } } (2)
]) {
title: String!
content: String!
authors: [User!]! @relationship(type: "AUTHORED", direction: IN)
}
type User @node @authorization(validate: [
{ operations: [CREATE_RELATIONSHIP], when: [BEFORE], where: { node: { locked: { eq: false } } } } (3)
{ operations: [CREATE_RELATIONSHIP], when: [AFTER], where: { node: { locked: { eq: false } } } } (4)
]) {
id: ID!
locked: Boolean!
}
| 1 | Rule 1 |
| 2 | Rule 2 |
| 3 | Rule 3 |
| 4 | Rule 4 |
When executing the following query, rules 2, 3, and 4 are applied:
mutation {
createPosts(
input: [{ title: "My Post", content: "Amazing content", authors: { connect: { where: { node: { id: { eq: "some-id" } } } } } }]
) {
posts {
title
}
}
}
Rule 1 isn’t applied because the relationship has not been created yet, so there is no author of the post to check the ID of.
Authorization on fields
The @authorization directive can be used either on object types or their fields, with the former being used in examples for the most part on this page.
When applied to a field, the authorization rules are only evaluated if the matching operations are performed on that field.
For example, consider a User type with a password field:
type User @node {
id: ID!
username: String!
password: String! @authorization(validate: [{ operations: [READ, UPDATE], where: { node: { id: { eq: "$jwt.sub" } } } }])
}
When executing the following query, a valid identity is not needed:
{
users {
username
}
}
However, consider the following query:
{
users {
username
password
}
}
This will require a valid JWT to have been provided with the request, and the matching users will be filtered down according to the JWT subject.
The same applies for attempting to update the password field, the update will only apply to the user matching the JWT.
|
Field-level For example, given the type definition above, the following query is not protected by
A non-empty result confirms the guess is correct, and an attacker can repeat this one character at a time to reconstruct the entire field value.
Sorting on the field ( To prevent a protected field from being filtered or sorted on, add the
|
Authorization without authentication
Authentication is implicitly required for every authorization check by default, but this can be disabled on a per-rule basis. This could be the case, for instance, when a node has a property which flags whether the node should be public or not.
For instance, in the case where some Post nodes are private and belong to a particular User, while other Post nodes are public and readable by any user, here is how to set this up:
type User @node {
id: ID!
}
type Post @node @authorization(filter: [
{
requireAuthentication: false,
operations: [READ],
where: { node: { public: { eq: true } } }
}
]) {
title: String!
content: String!
public: Boolean!
authors: [User!]! @relationship(type: "AUTHORED", direction: IN)
}
Ordering of rules
In each ruleset (filter and validate), rules are joined with an OR.
The two rulesets are joined with an AND.
For example, the following would allow for the update of a User node if the JWT roles claim includes admin or if the locked property on the node is false:
type User @node @authorization(validate: [
{ operations: [UPDATE], where: { jwt: { roles: { includes: "admin" } } } }
{ operations: [UPDATE], where: { node: { locked: { eq: false } } } }
]) {
id: ID!
locked: Boolean!
}
If you want to combine the rule that a user must be an admin with the rule that the locked property must be false in order to update a User node, add them both to the where field using AND in a single rule:
type User @node @authorization(validate: [
{ operations: [UPDATE], where: { AND: [{ jwt: { roles: { includes: "admin" } } }, { node: { locked: {eq: false} } }] } }
]) {
id: ID!
locked: Boolean!
}