Configuration

Instantiation

The Neo4j GraphQL Library can accept JSON Web Tokens via two mechanisms:

  • Encoded JWTs in the token field of the request context.

  • Decoded JWTs in the jwt field of the request context.

If using the former, the library will need to be configured with a key to decode and verify the token.

The following code block demonstrates using Apollo Server, extracting the Authorization header from the request, and putting it into the appropriate context field:

const server = new ApolloServer({
    schema, // schema from Neo4jGraphQL.getSchema()
});

const { url } = await startStandaloneServer(server, {
    listen: { port: 4000 },
    context: async ({ req }) => ({
        token: req.headers.authorization,
    }),
});

Optionally, if a custom decoding mechanism is required, that same header can be decoded and the resulting JWT payload put into the jwt field of the context.

Symmetric secret

To configure the library with a symmetric secret (e.g. "secret"), the following instantiation is required:

new Neo4jGraphQL({
    typeDefs,
    features: {
        authorization: {
            key: "secret",
        },
    },
});

JWKS endpoint

To configure the library to verify tokens against a JWKS endpoint, "https://www.myapplication.com/.well-known/jwks.json", the following instantiation is required:

new Neo4jGraphQL({
    typeDefs,
    features: {
        authorization: {
            key: {
                url: "https://www.myapplication.com/.well-known/jwks.json"
            },
        },
    },
});

JWT

By default, filtering is available on the registered claim names in the JWT specification.

Filtering can be configured for additional JWT claims using the @jwt directive and, in some circumstances, the @jwtClaim directive.

If you configure an additional roles claim, which is an array of strings located at the root of the JWT payload, the following must be added to the type definitions:

type JWT @jwt {
    roles: [String!]!
}

Note that the type name here, JWT, is not required, and this can have any name as long as it is decorated with the @jwt directive.

Nested claims

If the previous roles claim is not located at the JWT payload root, but instead in a nested location, for example:

{
    "sub": "user1234",
    "myApplication": {
        "roles": ["user", "admin"]
    }
}

This needs to be configured using the @jwtClaim directive:

type JWT @jwt {
    roles: [String!]! @jwtClaim(path: "myApplication.roles")
}

Additionally, if this nested location contains any . characters in the path, for example:

{
    "sub": "user1234",
    "http://www.myapplication.com": {
        "roles": ["user", "admin"]
    }
}

These characters need to be escaped:

type JWT @jwt {
    roles: [String!]! @jwtClaim(path: "http://www\\\\.myapplication\\\\.com.roles")
}

The seemingly excessive escaping is required to doubly escape: once for GraphQL and once for dot-prop, which is used under the hood to resolve the path.

Passing in JWTs

To pass in an encoded JWT, you must use the token field of the context. When using Apollo Server, extract the authorization header into the token property of the context as follows:

const server = new ApolloServer({
    schema,
});

await startStandaloneServer(server, {
    context: async ({ req }) => ({ token: req.headers.authorization }),
});

For example, a HTTP request with the following authorization header should look like this:

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

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.