> ## Documentation Index
> Fetch the complete documentation index at: https://docs.westyx.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# .NET SDK - Secrets binding

> Bind Nexus secrets to IOptions<T> with live reload, without placing them in IConfiguration.

*Package: `WestyxNexus.Extensions.DependencyInjection` (v0.11.0+)*

Bind Nexus secrets to a strongly-typed options class, with live reload on rotation - and **without** putting them into `IConfiguration`.

```csharp theme={null}
builder.Services.AddWestyxNexus(o => { /* ... */ });
builder.Services.AddWestyxNexusSecrets<StripeOptions>("stripe");
```

```csharp theme={null}
// Nexus secrets: "stripe.api_key", "stripe.webhook_secret"
public sealed class StripeOptions
{
    public string ApiKey        { get; set; } = "";
    public string WebhookSecret { get; set; } = "";
}
```

```csharp theme={null}
public sealed class PaymentService(IOptionsMonitor<StripeOptions> stripe)
{
    // Picks up a rotated secret on the next sync - no restart.
    private string Key => stripe.CurrentValue.ApiKey;
}
```

`IOptions<T>`, `IOptionsSnapshot<T>` and `IOptionsMonitor<T>` are all registered.

## Why not just put secrets in `IConfiguration`?

If you know the Azure Key Vault configuration provider you will expect exactly that, so this deserves an explanation rather than a shrug.

**.NET has no way to mark a configuration value as secret.** Once a value is in the configuration tree:

* the parameterless `IConfigurationRoot.GetDebugView()` prints it,
* `configuration.AsEnumerable()` walks it,
* the developer exception page displays it.

The `GetDebugView(Func<ConfigurationDebugViewContext, string>)` overload *can* mask values by originating provider - but only if the **caller** opts in, and an SDK cannot force that. On the conventional design, whether your Stripe key ends up in a log line or an error page depends on the discipline of every piece of code that touches the configuration root, including code you did not write.

`AddWestyxNexusSecrets<T>()` removes the exposure **structurally** instead. `IOptions<T>` does not require `IConfiguration` - it can be satisfied by a factory - so the value never enters the configuration tree at all. You keep constructor injection, strong typing and live reload; you lose only the ability to dump it by accident.

## Key mapping

Secrets are matched by prefix. The remainder of the key becomes the property path.

| Nexus secret            | Prefix   | Binds to                        |
| ----------------------- | -------- | ------------------------------- |
| `stripe.api_key`        | `stripe` | `ApiKey`                        |
| `stripe.webhook_secret` | `stripe` | `WebhookSecret`                 |
| `stripe.webhook.secret` | `stripe` | `Webhook.Secret` (nested class) |
| `postgres.password`     | `stripe` | *not bound*                     |

Nexus keys are snake\_case by convention and .NET properties are PascalCase. The binder handles that mapping, so `api_key` finds `ApiKey`. Matching is case-insensitive.

## Live re-bind

Every registration subscribes to the client's post-sync notification, so a rotated secret reaches `IOptionsMonitor<T>.CurrentValue` on the next sync - within milliseconds when the SSE stream is connected.

## The escape hatch

If you genuinely need secrets reachable through `IConfiguration`:

```csharp theme={null}
builder.Configuration.AddWestyx(config, includeSecrets: true);   // default: false
```

Values are placed under the `Secrets:` prefix.

<Warning>
  By enabling `includeSecrets` you accept everything described above: the values become visible to `GetDebugView()`, `AsEnumerable()` and the developer exception page.
</Warning>

A masking helper ships alongside it:

```csharp theme={null}
var safe = ((IConfigurationRoot)builder.Configuration).GetNexusSafeDebugView();
```

It redacts anything originating from a Nexus provider - but note it only helps where *you* call it.

## What was rejected

A `NexusSecretValue` wrapper type whose `ToString()` returns `"[REDACTED]"` was considered and turned down. It is a breaking change to `GetSecretAsync`, and in the case that actually matters - a secret bound into a POCO `string` property - it provides no protection at all. A half-measure that creates false confidence is worse than none.

## Next

* [Dependency injection](/sdks/dotnet/dependency-injection)
* [Design decisions](/sdks/dotnet/design-decisions)
