Allow

Use allow to ensure that on matched nodes, a connection exists between a value on the JWT and a property on each matched node. Taking a closer look, let’s create two users in a hypothetical empty database;

CREATE (:User {id:"user1", name: "one"})
CREATE (:User {id:"user2", name: "two"})
type User {
    id: ID!
    name: String!
}

Now we have two users in our database, and given the above GraphQL type definitions - How can we restrict user1 from accessing user2? This is where allow comes in;

type User {
    id: ID!
    name: String!
}

extend type User @auth(
    rules: [
        {
            operations: [READ],
            allow: { id: "$jwt.sub" }
        }
    ]
)

After we match the node, we validate that the property on the node is equal to the jwt.sub property. This validation is done in Cypher with two functions; validatePredicate and validate.

Given user1 has the decoded JWT;

{
  "sub": "user1",
  "iat": 1516239022
}

With this JWT makes a GraphQL query to get user2;

query {
    users(where: { id: "user2" }) {
        name
    }
}

The generated cypher for this query would look like the below and throw you out the operation.

MATCH (u:User {id: "user2"})
CALL apoc.util.validate(NOT(u.id = "user1"), "Forbidden")
RETURN u

Allow is used on the following operations;

  1. read

  2. update

  3. connect

  4. disconnect

  5. delete

1. allow Across Relationships

There may be a reason where you need to traverse across relationships to satisfy your auth implementation. One example of this could be "Grant update access to all Moderators of a Post";

type User {
    id: ID
    name: String
}

type Post {
    content: String
    moderators: [User] @relationship(type: "MODERATES_POST", direction: IN)
}

extend type Post @auth(rules: [
    { operations: [UPDATE], allow: { moderators: { id: "$jwt.sub" } } }
])

When you specify allow on a relationship you can select fields on the referenced node. It’s worth pointing out that allow on a relationship will perform an ANY on the matched nodes; to see if there is a match.

Given the above example - There may be a time when you need to give update access to either the creator of a post or a moderator, you can use OR and AND inside allow;

type User {
    id: ID
    name: String
}

type Post {
    content: String
    moderators: [User] @relationship(type: "MODERATES_POST", direction: IN)
    creator: User @relationship(type: "HAS_POST", direction: IN)
}

extend type Post
    @auth(
        rules: [
            {
                operations: [UPDATE],
                allow: { OR: [{ moderators: { id: "$jwt.sub" } }, { creator: { id: "$jwt.sub" } }] }
            }
        ]
    )

2. Field Level allow

Allow works the same as it does on Type Definitions although its context is the Field. So instead of enforcing auth rules when the node is matched and/or upserted, it would instead be called when the Field is selected or upserted. Given the following, it is hiding the password to only the user themselves;

type User {
    id: ID!
    name: String!
    password: String! @auth(rules: [{ allow: { id: "$jwt.sub" } }])
}