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

ITLab.Template.DevPack.Identity.MultiTenancy

Provides the tenant resolution pipeline: contracts, contributor base classes, context and result types.

Componentes

TipoNomeResponsabilidade
InterfaceITenantResolverCoordinates registered contributors and produces a TenantResolveResult
InterfaceITenantResolveContributorContract for a single tenant resolution strategy
ClassTenantResolveContributorBaseBase class with shared contributor lifecycle
Abstract ClassHttpTenantResolveContributorBaseBase for HTTP-based contributors; resolves the HttpContext and swallows resolution errors
ClassDomainTenantResolveContributorResolves the tenant from the request host (Origin → Referer → Host) via DomainFormats/AlternateDomains
Static ClassMultiTenancyOptionsExtensionsAddDomainTenantResolver(...) — registers the domain contributor at the front of the pipeline
InterfaceITenantResolveContextProvides request-scoped data for contributors to inspect
ClassTenantResolveContextDefault implementation of ITenantResolveContext
ClassTenantResolutionInfoCarries the resolved tenant identifier and contributing source
ClassTenantResolveResultFinal outcome of the resolution pipeline (resolved / unresolved)
ClassTenantResolveOptionsConfigures the ordered list of active contributors
InterfaceITenantAccessValidatorValidates 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:

  1. Claim-based (default, sem este pipeline): o tenant viaja como claim tenant no principal autenticado; o bridge UseDevPackIdentityContext o lê e popula ICurrentUser.TenantId (ver ../README.md § Claim de tenant). É o caminho mais simples e o adotado pelo template ITLab.Template.Next (ADR-009) — o tenant deriva do AppUser no login.

  2. 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 um ITenantResolveContributor e 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:

  1. DomainTenantResolveContributor extrai o host (OriginRefererRequest.Host) e o casa contra MultiTenancyOptions.DomainFormats (padrões com placeholder, e.g. "{0}.app.example.com") e AlternateDomains (overrides literais host→tenant, consultados antes dos padrões). Registre-o com options.AddDomainTenantResolver(formats, alternateDomains, serviceProvider).
  2. Middleware/TenantResolutionMiddleware (app.UseDevPackTenantResolution()) amarra resolver → ITenantAccessValidator.GetTenantIdByDomainNameAsyncValidateAccessAsynccross-check do id resolvido contra ICurrentUser.TenantId (o tenant do token). É no-op salvo MultiTenancyOptions.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 ITenantAccessValidator when 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 ITenantResolveContributor for product-specific resolution strategies (e.g. subdomain, API key).
  • Products implement ITenantAccessValidator to enforce tenant isolation rules.