Setup

Configuration

If you want the Neo4j GraphQL Library to perform JWT decoding and verification for you, you must pass the configuration option jwt into the Neo4jGraphQL or OGM constructor, which has the following arguments:

  • secret - The secret to be used to decode and verify JWTs

  • noVerify (optional) - Disable verification of JWTs, defaults to false

  • rolesPath (optional) - A string key to specify where to find roles in the JWT, defaults to "roles"

The simplest construction of a Neo4jGraphQL instance would be:

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

It is also possible to pass in JWTs which have already been decoded, in which case the jwt option is not necessary. This is covered in the section Passing in JWTs below.

Auth Roles Object Paths

If you are using a 3rd party auth provider 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"]
    }
}

In order to make use of this, you must pass it in as a "dot path" into the rolesPath option:

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

Passing in JWTs

If you wish to pass in an encoded JWT, this must be included in the authorization header of your requests, in the format:

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

Note the string "Bearer" before the inclusion of the JWT.

Then, using Apollo Server as an example, you must include the request in the GraphQL context, as follows (using the neoSchema instance from the example above):

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

Note that the request key req is appropriate for Express servers, but different middlewares use different keys for request objects. You can more details at https://www.apollographql.com/docs/apollo-server/api/apollo-server/#middleware-specific-context-fields.

Decoded JWTs

Alternatively, you can pass a key jwt of type JwtPayload into the context, which has the following definition:

// standard claims https://datatracker.ietf.org/doc/html/rfc7519#section-4.1
interface JwtPayload {
    [key: string]: any;
    iss?: string | undefined;
    sub?: string | undefined;
    aud?: string | string[] | undefined;
    exp?: number | undefined;
    nbf?: number | undefined;
    iat?: number | undefined;
    jti?: string | undefined;
}

Do not pass in the header or the signature.

For example, you might have a function decodeJWT which returns a decoded JWT:

const decodedJWT = decodeJWT(encodedJWT)

const server = new ApolloServer({
    schema: neoSchema.schema,
    context: { jwt: decodedJWT.payload },
});

Auth and Custom Resolvers

You can’t use the @auth directive on custom resolvers, however, the an auth parameter is injected into the context for use in them. It will be available under the auth property. For example, the following custom resolver returns the sub field from the JWT:

const typeDefs = `
    type Query {
        myId: ID!
    }
`;

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

Auth and @cypher fields

You can put the @auth directive on a field alongside the @cypher directive. Functionality like allow and bind will not work but you can still utilize isAuthenticated and roles. Additionally, you don’t need to specify operations for @auth directives on @cypher fields.

The following example uses the isAuthenticated rule to ensure a user is authenticated, before returning the User associated with the JWT:

type User @exclude {
    id: ID
    name: String
}

type Query {
    me: User
        @cypher(statement: "MATCH (u:User { id: $auth.jwt.sub }) RETURN u")
        @auth(rules: [{ isAuthenticated: true }])
}

In the following example, the current user must have role "admin" in order to query the history field on the type User:

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"] }])
}