ITLab.Template.DevPack.Identity.MultiTenancy
Provides the tenant resolution pipeline: contracts, contributor base classes, context and result types.
Componentes
| Tipo | Nome | Responsabilidade |
|---|---|---|
| Interface | ITenantResolver | Coordinates registered contributors and produces a TenantResolveResult |
| Interface | ITenantResolveContributor | Contract for a single tenant resolution strategy |
| Class | TenantResolveContributorBase | Base class with shared contributor lifecycle |
| Abstract Class | HttpTenantResolveContributorBase | Base for HTTP-based contributors; resolves the HttpContext and swallows resolution errors |
| Class | DomainTenantResolveContributor | Resolves the tenant from the request host (Origin → Referer → Host) via DomainFormats/AlternateDomains |
| Static Class | MultiTenancyOptionsExtensions | AddDomainTenantResolver(...) — registers the domain contributor at the front of the pipeline |
| Interface | ITenantResolveContext | Provides request-scoped data for contributors to inspect |
| Class | TenantResolveContext | Default implementation of ITenantResolveContext |
| Class | TenantResolutionInfo | Carries the resolved tenant identifier and contributing source |
| Class | TenantResolveResult | Final outcome of the resolution pipeline (resolved / unresolved) |
| Class | TenantResolveOptions | Configures the ordered list of active contributors |
| Interface | ITenantAccessValidator | Validates whether the current user is allowed to access a resolved tenant |
Como usar
// Register tenant resolution in DI.
services.Configure<TenantResolveOptions>(opts =>
{
opts.TenantResolvers.Add<HeaderTenantResolveContributor>();
opts.TenantResolvers.Add<ClaimsTenantResolveContributor>();
});
services.AddScoped<ITenantResolver, TenantResolver>();
// Resolve tenant in middleware or a pipeline behavior.
var result = await tenantResolver.ResolveAsync(context);
if (result.TenantIdOrName is not null)
{
// The DevPack does not impose a single way to apply the resolved tenant.
// Consumers store it in their tenant accessor, attach it as a claim, or
// forward it to a custom `ICurrentUser` implementation. There is no
// built-in `ICurrentUser.SetTenant(...)` method.
}
Resolução claim-based (default do canon) vs. contributors
Há dois caminhos para alimentar ICurrentUser.TenantId, e eles convivem:
-
Claim-based (default, sem este pipeline): o tenant viaja como claim
tenantno principal autenticado; o bridgeUseDevPackIdentityContexto lê e populaICurrentUser.TenantId(ver../README.md§ Claim de tenant). É o caminho mais simples e o adotado pelo templateITLab.Template.Next(ADR-009) — o tenant deriva doAppUserno login. -
Contributors (este pipeline): para resolver o tenant por header (
X-Tenant-Id), subdomínio/domínio ou route data — quando o tenant não vem (só) do token. Registre umITenantResolveContributore aplique o resultado ao seu accessor/claim.
O claim-based ativa o read-side filter + write-side interceptor automaticamente. O caminho de contributors entrega o TenantResolveResult; aplicá-lo ao ICurrentUser/claim continua sendo do consumidor (o DevPack não impõe um único mecanismo — ver abaixo).
Resolução por domínio/subdomínio (opt-in) + glue middleware
A partir da 10.6.0 o DevPack ship um caminho de domínio de primeira classe:
DomainTenantResolveContributorextrai o host (Origin→Referer→Request.Host) e o casa contraMultiTenancyOptions.DomainFormats(padrões com placeholder, e.g."{0}.app.example.com") eAlternateDomains(overrides literais host→tenant, consultados antes dos padrões). Registre-o comoptions.AddDomainTenantResolver(formats, alternateDomains, serviceProvider).Middleware/TenantResolutionMiddleware(app.UseDevPackTenantResolution()) amarra resolver →ITenantAccessValidator.GetTenantIdByDomainNameAsync→ValidateAccessAsync→ cross-check do id resolvido contraICurrentUser.TenantId(o tenant do token). É no-op salvoMultiTenancyOptions.EnableDomainValidation == true.
// DI — domain contributor + resolver + (consumer-implemented) access validator.
services.AddHttpContextAccessor();
services.AddOptions<TenantResolveOptions>()
.Configure<IServiceProvider>((opts, sp) =>
{
var settings = sp.GetRequiredService<IOptions<MultiTenancyOptions>>().Value;
opts.AddDomainTenantResolver(settings.DomainFormats, settings.AlternateDomains, sp);
});
services.AddScoped<ITenantResolver, TenantResolver>();
services.AddScoped<ITenantAccessValidator, MyTenantAccessValidator>();
// Pipeline — after the identity bridge, before authorization (ver Middleware/README.md).
app.UseDevPackIdentityContext();
app.UseDevPackTenantResolution();
app.UseAuthorization();
O cross-check é a peça de segurança: impede um token válido do tenant A de ser usado num host do tenant B. A ordem (depois do bridge) é obrigatória — o cross-check lê ICurrentUser.TenantId. Ver ../../Middleware/README.md § Domain tenant resolution e ADR-007.
O que não fazer
- Do not bypass
ITenantAccessValidatorwhen switching tenants on behalf of a user. - Do not hardcode tenant identifiers in contributors — source them from headers, claims or route data.
Extensão pelo produto
- Products implement
ITenantResolveContributorfor product-specific resolution strategies (e.g. subdomain, API key). - Products implement
ITenantAccessValidatorto enforce tenant isolation rules.