Setup

The auth implementation uses JWT tokens. You are expected to pass a JWT into the request. The accepted token type should be Bearer where the header should be authorization;

POST / HTTP/1.1
authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJyb2xlcyI6WyJ1c2VyX2FkbWluIiwicG9zdF9hZG1pbiIsImdyb3VwX2FkbWluIl19.IY0LWqgHcjEtOsOw60mqKazhuRFKroSXFQkpCtWpgQI
content-type: application/json

1. Config

Auth centric values on the Config object passed to Neo4jGraphQL or OGM.

Table 1. Auth Config
Variable Usage

secret

Specify JWT secret

noVerify

Disable the verification of the JW

rolesPath

Specify where on the JWT the roles key is

2. Server Construction

Request object needs to be injected into the context before you can use auth. Here is an example using Apollo Server;

const neoSchema = new Neo4jGraphQL({
    typeDefs,
    config: {
        jwt: {
            secret
        }
    }
});

const server = new ApolloServer({
    schema: neoSchema.schema,
    context: ({ req }) => ({ req }),
});

3. rules

You can have many rules for many operations. We fall through each rule, on the corresponding operation, until we find a match. On no match found, an error is thrown. You can think of rules as a big OR.

@auth(rules: [
    { operations: [CREATE, UPDATE], ... }, ## or
    { operations: [READ, UPDATE], ...}, ## or
    { operations: [DELETE, UPDATE], ... } ## or
])

4. operations

Operations is an array, you can re-use the same rule for many operations.

@auth(rules: [
    { operations: [CREATE, UPDATE, DELETE, CONNECT, DISCONNECT] },
    { operations: [READ] }
])

The absence of an operations argument will imply all operations

Many different operations can be called in one query take the below mutation;

mutation {
    createPosts(
        input: [
            {
                content: "I like GraphQL",
                creator: { connect: { where: { id: "user-01" } } }
            }
        ]
    ) {
        posts {
            content
        }
    }
}

In the above example; First we do a create operation then we do a connect operation.

The full list of operations are;

  1. read - MATCH

  2. create - CREATE

  3. update - SET

  4. delete - DELETE

  5. connect - MATCH & MERGE

  6. disconnect - MATCH & DELETE

5. Auth Roles Object Paths

If you are using 3rd party Auth solutions such as Auth0 you may find your roles property being nested inside an object;

{
    "https://auth0.mysite.com/claims": {
        "https://auth0.mysite.com/claims/roles": ["admin"]
    }
}

Specify the key at construction:

const neoSchema = new Neo4jGraphQL({
    typeDefs,
    config: {
        jwt: {
            secret,
            rolesPath: "https://auth0.mysite.com/claims\\.https://auth0.mysite.com/claims/roles"
        }
    }
});

6. Auth Value Plucking

  1. $jwt. - Pulls value from jsonwebtoken

  2. $context. - Pulls value from context

7. Auth Custom Resolvers

You cant put the auth directive on a custom resolver. We do make life easier by injecting the auth param into it. It will be available under the context.auth property;

const { Neo4jGraphQL } = require("@neo4j/graphql")
const neo4j = require("neo4j-driver");
const { ApolloServer } = require("apollo-server")

const typeDefs = `
    type User {
        id: ID!
        email: String!
        password: String!
    }
    type Query {
        myId: ID!
    }
`;

const driver = neo4j.driver(
    "bolt://localhost:7687",
    neo4j.auth.basic("admin", "password")
);

const resolvers = {
    Query: {
        myId(root, args, context) {
            return context.auth.jwt.sub
        }
    }
};

const neoSchema = new Neo4jGraphQL({ typeDefs, resolvers, config: { jwt } });

const server = new ApolloServer({
    schema: neo4jGraphQL.schema,
    context: ({ req }) => ({ req, driver }),
});

server.listen(4000).then(() => console.log("online"));

8. Auth on @cypher

You can put the @auth directive on a field with the @cypher directive. Functionality like allow and bind will not work but you can still utilize isAuthenticated and roles.

type User @exclude {
    id: ID
    name: String
}
type Query {
    users: [User] @cypher(statement: "MATCH (a:User) RETURN a") @auth(rules: [{ isAuthenticated: true }])
}

Notice you don’t need to specify operations for @auth directives on @cypher fields.

type History @exclude {
    website: String!
}
type User {
    id: ID
    name: String
    history: [History]
        @cypher(statement: "MATCH (this)-[:HAS_HISTORY]->(h:History) RETURN h")
        @auth(rules: [{ roles: ["admin"] }])
}