Pular para o conteúdo principal
Versão: main (dev) 🚧

ITLab.Template.DevPack.Identity.Settings

Contract for reading and writing per-tenant key/value settings, with transparent secret protection.

Componentes

TipoNomeResponsabilidade
InterfaceIPerTenantSettingsStoreGet/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 IPerTenantSettingsStore over their own settings entity, using ISecretProtector for mustProtect values and an IMemoryCache (short TTL + invalidate on write) for hot reads.