Configuration

The Neo4j GraphQL Library uses JSON Web Token (JWT) authentication. JWTs are tokens containing claims or statements about the user or client making the request. These claims can include information such as the user’s ID or roles.

A JWT can be obtained from an authentication service and then be included in an API request. The API verifies the JWT and returns the requested data if the JWT is valid.

Instantiation

The Neo4j GraphQL Library can accept two types of JWTs:

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

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

Encoded JWTs

In order to use encoded JWTs, configure the library with a key to decode and verify the tokens. The following code block uses Apollo Server. It extracts the Authorization header from the request and puts it in 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.

Alternatively, you can decode a token via a JWKS endpoint.

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 JSON Web Key Set (JWKS) endpoint, for example "https://www.example.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"
            },
        },
    },
});

Passing in encoded JWTs

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

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.

Decoded JWTs

A decoded JWT is passed to the context in a similar way that an encoded JWT is. However, instead of using a token, it uses the jwt field:

const jwt = customImplementation();

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

customImplementation is a placeholder for a function that provides a decoded JWT. Using jwt instead of token in the context informs the Neo4j GraphQL Library that it doesn’t need to decode it.

Adding JWT claims

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.

The @jwt directive

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

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

The type name JWT is not mandatory. You can use any name as long as it is decorated with the @jwt directive.

The @jwtClaim directive

A roles claim is not necessarily located at the JWT payload root. It can instead be in a nested location, for example under myApplication:

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

In this case, use the @jwtClaim directive alongside the @jwt directive:

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

Additionally, the nested location may contain . characters in the path, for example:

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

These characters must be escaped:

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

The path must be escaped twice: once for GraphQL and once for dot-prop, which is used under the hood to resolve the path.