ITLab.Template.DevPack.Identity.Settings
Contract for reading and writing per-tenant key/value settings, with transparent secret protection.
Componentes
| Tipo | Nome | Responsabilidade |
|---|---|---|
| Interface | IPerTenantSettingsStore | Get/GetByPrefix/Set per-tenant settings; secrets encrypted at rest, returned decrypted |
Como usar
The store is the seam for per-tenant configuration (e.g. which auth providers a tenant enabled, an
external IdP's client id/secret). The interface is universal; the implementation is consumer-specific
(typically EF Core over a TenantSetting entity, encrypting secret values via
ISecretProtector and caching per tenant).
// Read (decrypted) — e.g. the public auth-config endpoint or a per-tenant OnTokenValidated event.
var enabled = await store.GetAsync(tenantId, "AuthProvider.DefaultProvider"); // "true"
var entra = await store.GetByPrefixAsync(tenantId, ["AuthProvider.", "EntraID."]);
// Write — secret values are encrypted at rest and never returned encrypted.
await store.SetAsync(tenantId, "AuthProvider.DefaultProvider", "true");
await store.SetAsync(tenantId, "EntraID.WebApiClientSecret", secret, mustProtect: true);
store.InvalidateCache(tenantId);
Dynamic per-tenant auth scheme validation (recipe)
ASP.NET Core authentication schemes are static (registered at startup) — you cannot register a scheme
per request. To support per-tenant external IdPs, register fixed static schemes (DefaultProvider,
and later EntraId/Ldap) and apply the per-tenant configuration in the handler events, which run
per request with access to HttpContext + DI. Read the tenant's settings there via
IPerTenantSettingsStore:
// Static scheme registered once; per-tenant validation happens in the event.
.AddJwtBearer("EntraId", options =>
{
options.Events = new JwtBearerEvents
{
OnTokenValidated = async ctx =>
{
var store = ctx.HttpContext.RequestServices.GetRequiredService<IPerTenantSettingsStore>();
var tenantId = /* resolved pre-auth from the host (domain resolution) */;
var expectedClientId = await store.GetAsync(tenantId, "EntraID.WebApiClientId");
if (ctx.Principal?.FindFirst("aud")?.Value != expectedClientId)
ctx.Fail("Token audience does not match the tenant's registered client.");
},
};
});
This keeps schemes static while the configuration is per-tenant and dynamic.
O que não fazer
- Do not register an authentication scheme per request — schemes are static; validate per-tenant in events.
- Do not return or log secret values in their encrypted form — the store decrypts on read; never log plaintext either.
- Do not bypass the store to read raw settings rows for secrets — you'd skip decryption.
Extensão pelo produto
- Products implement
IPerTenantSettingsStoreover their own settings entity, usingISecretProtectorformustProtectvalues and anIMemoryCache(short TTL + invalidate on write) for hot reads.