# Client Configuration and TLS (/integrations/aspire/how-to/configuration-and-tls)



The `KubeMQ.Aspire.Client` package exposes every client setting through two configuration surfaces that you can mix freely: a JSON section in `appsettings.json` and inline delegates on the registration call. This guide walks through the connection string resolution order, the core SDK passthrough settings, enabling TLS, the startup TLS warnings, and the gRPC, keepalive, and reconnect tuning knobs — all grounded in the actual settings type and binding logic.

## Prerequisites [#prerequisites]

* An Aspire AppHost project with the KubeMQ resource added, and a service project referencing `KubeMQ.Aspire.Client` (see [Getting Started with .NET Aspire](/integrations/aspire/tutorials/getting-started))
* `builder.AddKubeMQClient(...)` (or `AddKeyedKubeMQClient`) already called in the service's `Program.cs`

## Two Configuration Surfaces [#two-configuration-surfaces]

Settings live in the `Aspire:KubeMQ:Client` section of `appsettings.json`. At startup `AddKubeMQClient` binds that section into a `KubeMQClientSettings` instance, then invokes the optional `configureSettings` delegate so code can override or supplement what came from configuration. A second `configureOptions` delegate runs last and operates on the raw SDK `KubeMQClientOptions`, giving you an escape hatch for anything the Aspire settings layer does not surface.

```csharp title="Program.cs"
var builder = WebApplication.CreateBuilder(args);

// 1. binds Aspire:KubeMQ:Client from appsettings.json
// 2. configureSettings overrides bound values
// 3. configureOptions tweaks the SDK options directly (runs last)
builder.AddKubeMQClient(
    "messaging",
    configureSettings: settings => settings.ClientId = "order-service",
    configureOptions: options => { /* low-level SDK options */ });

var app = builder.Build();
app.Run();
```

The signature is the same for the keyed overload, which binds from a per-name subsection (`Aspire:KubeMQ:Client:<name>`) instead of the top-level section:

```csharp title="Keyed registration"
builder.AddKeyedKubeMQClient("orders");
builder.AddKeyedKubeMQClient("notifications");
```

<Callout type="info">
  The binding order is fixed: appsettings.json first, then `configureSettings`, then `configureOptions`. A value set in a later step always wins over an earlier one.
</Callout>

## Connection String Resolution [#connection-string-resolution]

You normally do not set a connection string in the client at all — when the AppHost wires `WithReference(messaging)`, Aspire injects the resource's endpoint as a connection string named after the connection (`messaging`). The client resolves it in this order:

1. If `settings.ConnectionString` is set explicitly, that value wins.
2. Otherwise the client reads Aspire's injected connection string for the connection name via `GetConnectionString(connectionName)`.

The resolved value must be a plain `host:port` string with **no scheme**. The parser rejects anything containing `://`, and IPv6 literals must be bracketed as `[host]:port`.

```json title="appsettings.json — explicit override"
{
  "Aspire": {
    "KubeMQ": {
      "Client": {
        "ConnectionString": "kubemq.prod.internal:50000"
      }
    }
  }
}
```

| Input                    | Valid? | Notes                         |
| ------------------------ | ------ | ----------------------------- |
| `localhost:50000`        | Yes    | Standard IPv4 / hostname form |
| `[::1]:50000`            | Yes    | IPv6 must be bracketed        |
| `grpc://localhost:50000` | No     | Scheme prefix rejected        |
| `::1:50000`              | No     | Unbracketed IPv6 rejected     |
| `localhost`              | No     | Port is required              |

<Callout type="info">
  In a typical Aspire solution you let the AppHost supply the connection string. Set `ConnectionString` explicitly only when connecting to a broker that Aspire does not provision (for example a staging cluster).
</Callout>

## Core Settings [#core-settings]

These four settings pass straight through to the SDK. When left unset (`null`), the SDK applies its own documented defaults rather than the client forcing a value.

<TypeTable
  type="{
  AuthToken: { type: 'string?', description: 'gRPC authentication token sent to the broker. Default: null (unauthenticated).' },
  ClientId: { type: 'string?', description: 'Client identifier reported to the broker. Default: null (SDK generates one).' },
  DefaultTimeout: { type: 'TimeSpan?', description: 'SDK operation timeout. Null uses the SDK default of 5s.' },
  ConnectionTimeout: { type: 'TimeSpan?', description: 'SDK connection timeout. Null uses the SDK default of 10s.' },
}"
/>

```json title="appsettings.json — core settings"
{
  "Aspire": {
    "KubeMQ": {
      "Client": {
        "AuthToken": null,
        "ClientId": "order-service",
        "DefaultTimeout": "00:00:05",
        "ConnectionTimeout": "00:00:10"
      }
    }
  }
}
```

## Enabling TLS [#enabling-tls]

TLS is off by default so dev containers work over plain HTTP. Set `UseTls=true` to switch the gRPC connection to TLS; when it is true the client builds the SDK `TlsOptions` from the matching `Tls*` properties. All certificate fields are optional — omit them to verify against the system trust store, or supply PEM paths for mutual TLS.

<TypeTable
  type="{
  UseTls: { type: 'bool', description: 'Enable TLS for the gRPC connection.', default: 'false' },
  TlsCertFile: { type: 'string?', description: 'Path to the client certificate (PEM). For mutual TLS.', default: 'null' },
  TlsKeyFile: { type: 'string?', description: 'Path to the client private key (PEM). For mutual TLS.', default: 'null' },
  TlsCaFile: { type: 'string?', description: 'Path to the CA certificate (PEM) used to verify the server.', default: 'null' },
  TlsServerNameOverride: { type: 'string?', description: 'Override the server name used for certificate verification.', default: 'null' },
  TlsInsecureSkipVerify: { type: 'bool', description: 'Skip certificate verification. Development only.', default: 'false' },
}"
/>

```json title="appsettings.json — TLS with a CA file"
{
  "Aspire": {
    "KubeMQ": {
      "Client": {
        "ConnectionString": "kubemq.prod.internal:50000",
        "UseTls": true,
        "TlsCaFile": "/etc/kubemq/certs/ca.pem"
      }
    }
  }
}
```

For mutual TLS, add the client certificate and key:

```json title="appsettings.json — mutual TLS"
{
  "Aspire": {
    "KubeMQ": {
      "Client": {
        "UseTls": true,
        "TlsCaFile": "/etc/kubemq/certs/ca.pem",
        "TlsCertFile": "/etc/kubemq/certs/client.pem",
        "TlsKeyFile": "/etc/kubemq/certs/client-key.pem"
      }
    }
  }
}
```

<Callout type="warn">
  `TlsInsecureSkipVerify=true` disables certificate verification entirely and exposes the connection to man-in-the-middle attacks. Use it only against a local development broker, never in production.
</Callout>

## TLS Startup Warnings [#tls-startup-warnings]

A hosted service (`KubeMQTlsWarningHostedService`) runs at startup and inspects the resolved TLS configuration so a missing-TLS setup never goes unnoticed in the logs:

* If `UseTls` is **false**, it logs a **warning** advising you to set `UseTls=true` for production.
* If `UseTls` is false **and** an `AuthToken` is configured, it additionally logs a **Critical** message because the credentials would travel in plaintext.

```text title="Startup log — token without TLS"
warn: KubeMQ.Aspire.Client
      KubeMQ client 'messaging' is configured without TLS. Set UseTls=true for production environments.
crit: KubeMQ.Aspire.Client
      KubeMQ client 'messaging' has an authentication token configured without TLS.
      Credentials will be transmitted in plaintext. Enable UseTls=true to secure the connection.
```

The check is purely advisory — it never blocks startup — but the Critical entry is your signal that an `AuthToken` is leaking over an unencrypted channel. The clean fix is to set `UseTls=true` alongside the token.

## gRPC Tuning [#grpc-tuning]

These options shape the gRPC channel pool and message-size limits. Each is nullable; leaving it `null` keeps the SDK default.

<TypeTable
  type="{
  GrpcChannelCount: { type: 'int?', description: 'Number of gRPC channels to pool, 1-16. Null uses the SDK default of 5.', default: 'null' },
  MaxSendSize: { type: 'int?', description: 'Max send message size in bytes. Null uses the SDK default of 104857600 (100 MB).', default: 'null' },
  MaxReceiveSize: { type: 'int?', description: 'Max receive message size in bytes. Null uses the SDK default of 104857600 (100 MB).', default: 'null' },
  WaitForReady: { type: 'bool?', description: 'Block operations until the connection is ready. Null uses the SDK default of true.', default: 'null' },
}"
/>

```json title="appsettings.json — gRPC tuning"
{
  "Aspire": {
    "KubeMQ": {
      "Client": {
        "GrpcChannelCount": 8,
        "MaxSendSize": 209715200,
        "MaxReceiveSize": 209715200,
        "WaitForReady": true
      }
    }
  }
}
```

<Callout type="info">
  `GrpcChannelCount` is validated to the inclusive range 1–16 by `ConfigurationSchema.json`; the IDE flags out-of-range values before you ever run the app.
</Callout>

## Keepalive [#keepalive]

Keepalive pings keep idle gRPC connections healthy through load balancers and NAT timeouts. Both values are nullable and fall back to the SDK defaults.

<TypeTable
  type="{
  KeepalivePingInterval: { type: 'TimeSpan?', description: 'Interval between keepalive pings. Null uses the SDK default of 10s.', default: 'null' },
  KeepalivePingTimeout: { type: 'TimeSpan?', description: 'How long to wait for a ping ACK before treating the connection as dead. Null uses the SDK default of 5s.', default: 'null' },
}"
/>

```json title="appsettings.json — keepalive"
{
  "Aspire": {
    "KubeMQ": {
      "Client": {
        "KeepalivePingInterval": "00:00:10",
        "KeepalivePingTimeout": "00:00:05"
      }
    }
  }
}
```

## Reconnect [#reconnect]

The client auto-reconnects after a dropped connection. Tune the behavior with these nullable options.

<TypeTable
  type="{
  ReconnectEnabled: { type: 'bool?', description: 'Enable auto-reconnection. Null uses the SDK default of true.', default: 'null' },
  ReconnectMaxAttempts: { type: 'int?', description: 'Max reconnect attempts; 0 means unlimited. Null uses the SDK default of 0.', default: 'null' },
  ReconnectTimeout: { type: 'TimeSpan?', description: 'Timeout for each reconnect wait-for-ready. Null uses the SDK default of 60s.', default: 'null' },
}"
/>

```json title="appsettings.json — reconnect"
{
  "Aspire": {
    "KubeMQ": {
      "Client": {
        "ReconnectEnabled": true,
        "ReconnectMaxAttempts": 0,
        "ReconnectTimeout": "00:01:00"
      }
    }
  }
}
```

## Configuring via Delegate [#configuring-via-delegate]

Anything you can set in `appsettings.json` you can also set in code through the `configureSettings` delegate. This is handy for values that come from secrets, feature flags, or environment-specific logic.

```csharp title="Program.cs — settings delegate"
builder.AddKubeMQClient("messaging", settings =>
{
    settings.DisableHealthChecks = true;
    settings.AuthToken = "my-token";
});
```

Because the delegate runs after JSON binding, it overrides whatever was in the `Aspire:KubeMQ:Client` section. For multiple instances, pass a delegate to each keyed registration:

```csharp title="Program.cs — per-keyed settings"
builder.AddKeyedKubeMQClient("orders", settings => settings.ClientId = "orders-consumer");
builder.AddKeyedKubeMQClient("notifications", settings => settings.UseTls = true);
```

## Full appsettings.json Example [#full-appsettingsjson-example]

This example shows the default client section together with a keyed subsection. The keyed subsection name (`orders`) matches the service key passed to `AddKeyedKubeMQClient`. Because the package ships a `ConfigurationSchema.json`, your IDE provides IntelliSense, defaults, and range validation for every key below.

```json title="appsettings.json"
{
  "Aspire": {
    "KubeMQ": {
      "Client": {
        "DisableHealthChecks": false,
        "DisableTracing": false,
        "DisableMetrics": false,
        "HealthCheckTimeout": "00:00:05",
        "ClientId": "default-service",
        "DefaultTimeout": "00:00:05",
        "ConnectionTimeout": "00:00:10",
        "UseTls": true,
        "TlsCaFile": "/etc/kubemq/certs/ca.pem",
        "GrpcChannelCount": 5,
        "KeepalivePingInterval": "00:00:10",
        "KeepalivePingTimeout": "00:00:05",
        "ReconnectEnabled": true,
        "ReconnectMaxAttempts": 0,
        "ReconnectTimeout": "00:01:00",

        "orders": {
          "ConnectionString": "orders-broker:50000",
          "ClientId": "orders-consumer",
          "UseTls": true,
          "TlsCaFile": "/etc/kubemq/certs/ca.pem",
          "GrpcChannelCount": 8
        }
      }
    }
  }
}
```

To exercise the configuration locally against a plain broker, run KubeMQ with the gRPC port published:

<RunKubeMQ ports="[50000, 9090]" />

## License Key vs. AuthToken [#license-key-vs-authtoken]

These two tokens are easy to confuse but serve different roles:

| Token                        | Where it is set                          | Purpose                                                                 |
| ---------------------------- | ---------------------------------------- | ----------------------------------------------------------------------- |
| License key (`KUBEMQ_TOKEN`) | AppHost, via `WithLicenseKey()`          | Activates the KubeMQ **server** container. A server/deployment concern. |
| `AuthToken`                  | Client settings (`Aspire:KubeMQ:Client`) | Authenticates the **gRPC client** connection to the broker.             |

<Callout type="warn">
  The license key is not a client credential — set it on the server resource in the AppHost with `WithLicenseKey()`. The client `AuthToken` is the per-connection gRPC credential and belongs in the client settings. When you set an `AuthToken`, also enable `UseTls=true` so it is not sent in plaintext.
</Callout>

## Related [#related]

* [Getting Started with .NET Aspire](/integrations/aspire/tutorials/getting-started) for provisioning the broker and sending your first message
* [.NET Aspire integration overview](../) for the package model and API reference
