Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,27 @@ public class Subscription
}
```

## Sending Interface Types

To use an interface type as result of a subscription, be sure to specify the type when calling `sender.SendAsync`.

```csharp
public class Subscription
{
[Subscribe]
[Topic("ExampleTopic")]
public IBook IBookPublished([EventMessage] IBook book)
=> book;
}

public async Book PublishBook(Book book, ITopicEventSender sender)
{
await sender.SendAsync<IBook>("ExampleTopic", book);

// Omitted code for brevity
}
```

# Websocket Authentication

When working with GraphQL subscriptions over WebSockets, you may want to authenticate incoming WebSocket connections using JSON Web Tokens. Normally, HTTP headers are sent with each request for standard APIs, but WebSockets behave differently. After a successful HTTP handshake, the protocol is "upgraded" to WebSockets, and additional headers cannot be easily injected for subsequent messages.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -331,3 +331,80 @@ public class Subscription
=> book;
}
```

## Sending Interface Types

To use an interface type as result of a subscription, be sure to specify the type when calling `sender.SendAsync`.

```csharp
public class Subscription
{
[Subscribe]
[Topic("ExampleTopic")]
public IBook IBookPublished([EventMessage] IBook book)
=> book;
}

public async Book PublishBook(Book book, ITopicEventSender sender)
{
await sender.SendAsync<IBook>("ExampleTopic", book);

// Omitted code for brevity
}
```

# Websocket Authentication

When working with GraphQL subscriptions over WebSockets, you may want to authenticate incoming WebSocket connections using JSON Web Tokens. Normally, HTTP headers are sent with each request for standard APIs, but WebSockets behave differently. After a successful HTTP handshake, the protocol is "upgraded" to WebSockets, and additional headers cannot be easily injected for subsequent messages.

Instead, the recommended approach is to send your token via the `connection_init` message when the WebSocket connection is first established. Hot Chocolate allows you to intercept this initial message, extract the token, and then authenticate the user in a way similar to standard HTTP requests.

An example implementation of this approach can be found in the [Hot Chocolate Examples repository](https://github.com/ChilliCream/hotchocolate-examples/tree/master/misc/WebsocketAuthentication).

## Why a Special Approach for WebSockets

- **Single HTTP Handshake**: A WebSocket connection is established once. After that, you cannot update HTTP headers on the same connection.
- **`connection_init` Payload**: GraphQL subscription clients send a `connection_init` message when establishing the subscription. This payload can include extra properties (e.g., `authorization`), which Hot Chocolate can use for authentication.
- **Long-Lived Connections**: Because WebSockets are persistent, tokens might remain valid for the entire duration of the connection. It is advisable to ensure that you handle token expiration appropriately—often by closing the connection if security policies require it.

## Core Concepts

1. **Stub (or "Skip") Authentication Scheme**
The initial WebSocket upgrade request is directed to a "stub" authentication scheme that simply indicates "no authentication result" for upgrade requests. This prevents the request from failing before you can intercept and handle the token manually.

2. **Forwarding the Default Scheme**
In standard HTTP scenarios, the default scheme (e.g., JWT bearer) is used to authenticate. However, if the request is recognized as a WebSocket upgrade, the framework forwards it to the "stub" scheme first. That way, you don’t attempt to validate a token at the moment of the upgrade handshake.

3. **Intercepting `connection_init`**
Once the WebSocket is established, the client sends `connection_init` containing authentication data. A custom `SocketSessionInterceptor` (or similar) reads the token from `connection_init` (e.g., under a key like `authorization`), stores it in the `HttpContext`, and triggers a fresh authentication attempt—this time using the real JWT bearer scheme.

4. **Hot Chocolate Integration**
Hot Chocolate's subscription middleware allows you to plug into the subscription lifecycle. By customizing the session interceptor (`OnConnectAsync`), you can decide whether to accept or reject the connection based on successful authentication.

## Testing the Flow

1. **Open Nitro**
Use a local instance of Nitro (e.g., `https://localhost:5095/graphql`) to send GraphQL queries and subscriptions.

2. **Retrieve an Access Token**
Request a token from your `/token` endpoint. This endpoint should return a valid JWT that is trusted by your API.

3. **Configure Nitro**

- In Nitro, open the **Settings** of your document / API.
- Under **Authentication**, choose **Bearer Token** and paste your JWT.
- Nitro will automatically include the token in the `connection_init` message under an `authorization` parameter when opening a WebSocket connection.

4. **Run Your Subscription**
Execute the subscription query of your choice. For example:

```graphql
subscription {
onTimedEvent {
count
isAuthenticated
}
}
```

The server-side resolver can check `isAuthenticated` to demonstrate whether the current user is authenticated (based on the token you provided).
Original file line number Diff line number Diff line change
Expand Up @@ -331,3 +331,80 @@ public class Subscription
=> book;
}
```

## Sending Interface Types

To use an interface type as result of a subscription, be sure to specify the type when calling `sender.SendAsync`.

```csharp
public class Subscription
{
[Subscribe]
[Topic("ExampleTopic")]
public IBook IBookPublished([EventMessage] IBook book)
=> book;
}

public async Book PublishBook(Book book, ITopicEventSender sender)
{
await sender.SendAsync<IBook>("ExampleTopic", book);

// Omitted code for brevity
}
```

# Websocket Authentication

When working with GraphQL subscriptions over WebSockets, you may want to authenticate incoming WebSocket connections using JSON Web Tokens. Normally, HTTP headers are sent with each request for standard APIs, but WebSockets behave differently. After a successful HTTP handshake, the protocol is "upgraded" to WebSockets, and additional headers cannot be easily injected for subsequent messages.

Instead, the recommended approach is to send your token via the `connection_init` message when the WebSocket connection is first established. Hot Chocolate allows you to intercept this initial message, extract the token, and then authenticate the user in a way similar to standard HTTP requests.

An example implementation of this approach can be found in the [Hot Chocolate Examples repository](https://github.com/ChilliCream/hotchocolate-examples/tree/master/misc/WebsocketAuthentication).

## Why a Special Approach for WebSockets

- **Single HTTP Handshake**: A WebSocket connection is established once. After that, you cannot update HTTP headers on the same connection.
- **`connection_init` Payload**: GraphQL subscription clients send a `connection_init` message when establishing the subscription. This payload can include extra properties (e.g., `authorization`), which Hot Chocolate can use for authentication.
- **Long-Lived Connections**: Because WebSockets are persistent, tokens might remain valid for the entire duration of the connection. It is advisable to ensure that you handle token expiration appropriately—often by closing the connection if security policies require it.

## Core Concepts

1. **Stub (or "Skip") Authentication Scheme**
The initial WebSocket upgrade request is directed to a "stub" authentication scheme that simply indicates "no authentication result" for upgrade requests. This prevents the request from failing before you can intercept and handle the token manually.

2. **Forwarding the Default Scheme**
In standard HTTP scenarios, the default scheme (e.g., JWT bearer) is used to authenticate. However, if the request is recognized as a WebSocket upgrade, the framework forwards it to the "stub" scheme first. That way, you don’t attempt to validate a token at the moment of the upgrade handshake.

3. **Intercepting `connection_init`**
Once the WebSocket is established, the client sends `connection_init` containing authentication data. A custom `SocketSessionInterceptor` (or similar) reads the token from `connection_init` (e.g., under a key like `authorization`), stores it in the `HttpContext`, and triggers a fresh authentication attempt—this time using the real JWT bearer scheme.

4. **Hot Chocolate Integration**
Hot Chocolate's subscription middleware allows you to plug into the subscription lifecycle. By customizing the session interceptor (`OnConnectAsync`), you can decide whether to accept or reject the connection based on successful authentication.

## Testing the Flow

1. **Open Nitro**
Use a local instance of Nitro (e.g., `https://localhost:5095/graphql`) to send GraphQL queries and subscriptions.

2. **Retrieve an Access Token**
Request a token from your `/token` endpoint. This endpoint should return a valid JWT that is trusted by your API.

3. **Configure Nitro**

- In Nitro, open the **Settings** of your document / API.
- Under **Authentication**, choose **Bearer Token** and paste your JWT.
- Nitro will automatically include the token in the `connection_init` message under an `authorization` parameter when opening a WebSocket connection.

4. **Run Your Subscription**
Execute the subscription query of your choice. For example:

```graphql
subscription {
onTimedEvent {
count
isAuthenticated
}
}
```

The server-side resolver can check `isAuthenticated` to demonstrate whether the current user is authenticated (based on the token you provided).