Bind

Use bind to ensure that on creating or updating nodes, there is equality between a value on the JWT and a property on a matched node. This validation is done after the operation but inside a transaction. Taking a closer look, create a user in your database:

CREATE (:User { id:"user1", name: "one" })

For the label and properties of the node created above, the corresponding GraphQL type definitions would be:

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

Given the above GraphQL type definition - you could restrict user1 from changing their own ID:

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

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

After the update or creation of the node, it is validated that the property id on the node is equal to the jwt.sub property.

Given user1 has the following decoded JWT:

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

When the user makes a request using this JWT to change their ID:

mutation {
    updateUsers(where: { id: "user1" }, update: { id: "user2" }) {
        users {
            name
        }
    }
}

The generated cypher for this query would look like the below, throwing you out of the operation because the id property no longer matches.

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

Bind is available for the following operations;

  • READ

  • UPDATE

  • CONNECT

  • DISCONNECT

  • DELETE

bind across relationships

There may be a reason where you need to traverse across relationships to satisfy your authorization implementation. One use case could be "ensure that users only create Posts related to themselves":

type User {
    id: ID
    name: String
}

type Post {
    content: String
    creator: User! @relationship(type: "HAS_POST", direction: IN)
}

extend type Post @auth(rules: [
    { operations: [CREATE], bind: { creator: { id: "$jwt.sub" } } }
])

When you specify bind on a relationship you can select fields on the related node. It’s worth pointing out that bind on a relationship field will perform an all on the matched nodes to see if there is a match, or any if the bindPredicate option of the plugin has been set to "any".

Field-level bind

You can use bind on a field, and the root is still considered the node itself. Taking the example at the start of this chapter, you could do the following to implement the same behaviour:

type User {
    id: ID! @auth(rules: [{ operations: [UPDATE], bind: { id: "$jwt.sub" } }])
    name: String!
}