Skip to main content
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.
This is a default, not a restriction. Secrets stay out of IConfiguration unless you ask for them, and one parameter changes it:
That gives you the Azure Key Vault-style layout, with the values under a Secrets: prefix. Both paths are supported, documented and tested - pick the one your application needs. The rest of this page explains why the default is the other way round, and exactly what changes when you switch.
Needs a NexusClient in the container - see Wiring the packages together. builder.Configuration.AddWestyx(config) alone does not register one, because it keeps its client inside the configuration provider; without a registered client this call reports which registration is missing.
IOptions<T>, IOptionsSnapshot<T> and IOptionsMonitor<T> are all registered.

Declare the options type as a class, not a record

This is the one part of the path the SDK cannot secure for you, and it is worth thirty seconds of attention. Keeping the secret out of IConfiguration protects the configuration tree; the object the value is bound into is yours. A C# record gets a compiler-generated ToString() that prints the names and values of all public properties. So this compiles, binds, and writes your Stripe key to the log the first time anything formats the options object:
Nobody decided to log the secret - the type did it. Declare the options type as a sealed class, as every example on this page does, and the same log line prints the type name only. If you want a record for the value semantics, override ToString():
A positional record (public record StripeOptions(string ApiKey);) will not work regardless: the options pattern instantiates TOptions with Activator.CreateInstance<TOptions>(), which needs a public parameterless constructor and throws MissingMethodException without one.

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, together with the provider it came from,
  • configuration.AsEnumerable() walks it,
  • so does any code that enumerates the configuration root - a startup configuration dump, a hand-written /debug/config endpoint, a diagnostics or health package.
Microsoft’s own answer to this is the GetDebugView(Func<ConfigurationDebugViewContext, string>) overload, whose parameter is documented as “the function for processing the value, for example, hiding secrets”. It works - 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 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 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.

Secrets in IConfiguration (the Key Vault-style layout)

To have secrets reachable through IConfiguration - the layout the Azure Key Vault provider gives you:
Values are placed under the Secrets: prefix, so stripe.api_key becomes configuration["Secrets:stripe:api_key"], binds with GetSection("Secrets:stripe"), and participates in normal configuration layering. This is a supported configuration, not a workaround. What changes is one thing, and it is the reason the default is the other way round: the values are now part of the configuration tree, so GetDebugView(), AsEnumerable() and anything else that enumerates it can reach them. A masking helper ships for exactly that case:
It redacts anything originating from a Nexus provider. Use it wherever your application dumps configuration - it works only where you call it, which is the limitation the default avoids.

What was rejected

A NexusSecretValue wrapper type whose ToString() returns "[REDACTED]" was considered and turned down. It is a breaking change to GetSecret, 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