Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions modules/ROOT/pages/directives/index.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,10 @@ a| Required to differentiate interfaces that are used for relationship propertie
| xref::/security/authorization.adoc[`@authorization`]
| Specifies authorization rules for queries and mutations on the type.

| xref::/security/configuration.adoc#authentication-and-authorization-jwt[`@jwt`]
| xref::/security/configuration.adoc#_the_jwt_directive[`@jwt`]
| Configures the JWT authentication and authorization filters to include additional JWT claims.

| xref::/security/configuration.adoc#_nested_claims[`@jwtClaim`]
| xref::/security/configuration.adoc#_the_jwtclaim_directive[`@jwtClaim`]
| Used in combination with `@jwt`.
Configures the JWT authentication and authorization filters to include an additional JWT claim which is either nested or using special characters not supported by GraphQL.

Expand Down
149 changes: 89 additions & 60 deletions modules/ROOT/pages/security/configuration.adoc
Original file line number Diff line number Diff line change
@@ -1,16 +1,28 @@
= Configuration
:description: This page describes how to configure authentication and authorization features in the Neo4j GraphQL Library.

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.

When a user or client logs in to the API, the API generates a JWT and returns it to the client.
The client then includes the JWT with each subsequent request to the API.
The API verifies the JWT and returns the requested data if the JWT is valid.

// ^ is this paragraph accurate?

== Instantiation

The Neo4j GraphQL Library can accept JSON Web Tokens via two mechanisms:
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.

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

The following code block demonstrates using Apollo Server, extracting the `Authorization` header from the request, and putting it into the appropriate context field:
To use encoded JWTs, the library must to be configured with a key to decode and verify the tokens.
Copy link
Contributor

@mjfwebb mjfwebb Aug 29, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't the case.

An encoded token also can be decoded via a JWKS endpoint.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rephrased


The following code block uses Apollo Server, extracts the `Authorization` header from the request and puts it in the appropriate context field:

[source, typescript, indent=0]
----
Expand All @@ -28,10 +40,14 @@ const { url } = await startStandaloneServer(server, {

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
// ^ Can we show the above in a code listing?

==== Symmetric secret

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

// ^ What is a symmetric secret? What is its purpose?

[source, typescript, indent=0]
----
new Neo4jGraphQL({
Expand All @@ -44,9 +60,11 @@ new Neo4jGraphQL({
});
----

=== JWKS endpoint
==== 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:
To configure the library to verify tokens against a JWKS endpoint, for example "https://www.myapplication.com/.well-known/jwks.json", the following instantiation is required:

// ^ What is the purpose?

[source, typescript, indent=0]
----
Expand All @@ -62,14 +80,66 @@ new Neo4jGraphQL({
});
----

[[authentication-and-authorization-jwt]]
== JWT
==== Passing in encoded JWTs

// This was at the end of the file, I thought it could be moved here instead. What about decoded 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:

[source, javascript, indent=0]
----
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:

[source]
----
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:

[source, typescript, indent=0]
----
// 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;
}
----

[WARNING]
Do not pass in the header or the signature.

=== Decoded JWTs

// What could be added here?
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

^

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A decoded JWT is passed in to the context in a similar way that an encoded JWT is, but instead of using token, we use jwt, like this where the value of the jwt is passed in by code. customImplementation should be thought of as a placeholder for whatever the programmer would actually write.

const jwt = customImplementation();

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

Using jwt instead of token in the context informs the Neo4jGraphQL library that it doesn't need to decode it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

adapted, thanks


== Adding JWT claims

By default, filtering is available on https://www.rfc-editor.org/rfc/rfc7519#section-4.1[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:
=== 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:

[source, graphql, indent=0]
----
Expand All @@ -78,11 +148,12 @@ type JWT @jwt {
}
----

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.
Note that the type name `JWT` is not required.
You can use any name as long as it is decorated with the `@jwt` directive.

=== Nested claims
=== The `@jwtClaim` directive

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

[source, json, indent=0]
----
Expand All @@ -94,7 +165,9 @@ If the previous `roles` claim is not located at the JWT payload root, but instea
}
----

This needs to be configured using the `@jwtClaim` directive:
// ^ why is this a nested location? can we show the nesting?

In this case, use the `@jwtClaim` directive:

[source, graphql, indent=0]
----
Expand All @@ -103,7 +176,7 @@ type JWT @jwt {
}
----

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

[source, json, indent=0]
----
Expand All @@ -115,7 +188,7 @@ Additionally, if this nested location contains any `.` characters in the path, f
}
----

These characters need to be escaped:
Escape these characters:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the way you rephrase it makes it sound like it's an option, but it may not be the case?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

True. Rephrasing


[source, graphql, indent=0]
----
Expand All @@ -126,50 +199,6 @@ type JWT @jwt {

[NOTE]
====
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.
This way of escaping is necessary to escape twice: 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:

[source, javascript, indent=0]
----
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:

[source]
----
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:

[source, typescript, indent=0]
----
// 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;
}
----

[WARNING]
_Do not_ pass in the header or the signature.
4 changes: 2 additions & 2 deletions modules/ROOT/pages/security/index.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ auth/authorization/allow.adoc, auth/authorization/bind.adoc, auth/authorization/
auth/authorization/where.adoc, authentication-and-authorization/index.adoc


* xref::/security/configuration.adoc[Configuration] - Instructions to set up instantiation.
* xref::/security/authentication.adoc[Authentication] - Explicit authentication, configured using the `@authentication` directive.
* xref::/security/authorization.adoc[Authorization] - Authorization rules set using the `@authorization` directive.
* xref::/security/subscriptions-authorization.adoc[Subscriptions authorization] - Authorization rules for subscriptions set using the `@subscriptionsAuthorization` directive.
* xref::/security/configuration.adoc[Configuration] - Instructions to set up instantiation.
* xref::/security/subscriptions-authorization.adoc[Subscriptions authorization] - Authorization rules for subscriptions set using the `@subscriptionsAuthorization` directive.
* xref::/security/impersonation-and-user-switching.adoc[Impersonation and user switching] - How to set up impersonation and user switching features.
* xref::/security/operations.adoc[Operations] - Reference on GraphQL queries and how each location in each query triggers the evaluation of different authentication/authorization rules.