Skip to content

Tenancy Configuration

You can configure tenancy programmatically or through configuration files. Both approaches map to the same options and resolver types.

This is a configuration fragment for an existing ArcApplicationBuilder (Core) or WebApplicationBuilder (ASP.NET Core), before Build(). Import Cratis.Arc and Cratis.Arc.Tenancy; each host uses builder.AddCratisArc(...), followed by app.UseCratisArc() on the built app.

builder.AddCratisArc(options =>
{
options.UseHeaderTenancy("X-Custom-Tenant");
// options.UseQueryTenancy("tenant");
// options.UseClaimTenancy("tenant_id");
// options.UseSubdomainTenancy("myapp.com", "X-Custom-Tenant");
// options.UseFixedTenancy("acme");
// options.UseDevelopmentTenancy("test-tenant");
});
{
"Cratis": {
"Arc": {
"Tenancy": {
"ResolverType": "Header",
"HttpHeader": "X-Custom-Tenant"
}
}
}
}

Register an application-owned ITenantMembership when Arc must verify that the effective caller may use a request-selected tenant. There is no default implementation. Without a hook, header, query and subdomain resolution behaves as before and logs one startup warning. Claim, fixed, development and custom resolvers do not produce this warning.

Set Cratis:Arc:Tenancy:IngressValidatesTenant to true only when a trusted ingress actually enforces caller-to-tenant membership and direct backend bypass is prevented. Cratis AuthProxy’s optional tenant verification checks existence, not caller membership; deploying behind it alone does not satisfy this condition. The setting is an explicit trusted operator assertion, not topology autodetection. Its default remains false, and it only suppresses the startup diagnostic; it does not validate requests, change resolution or bypass a registered hook. The warning is not automatic protection. See tenant membership for registration, backend isolation assumptions, synchronous execution and default-tenant handling.

The following JSON fragments belong inside Cratis:Arc, not at the root of appsettings.json. The complete options tree lists defaults. Resolver configuration selects a tenant; membership authorization and storage isolation remain separate responsibilities.

{
"Tenancy": {
"ResolverType": "Header",
"HttpHeader": "X-Custom-Tenant"
}
}
{
"Tenancy": {
"ResolverType": "Query",
"QueryParameter": "tenant"
}
}
{
"Tenancy": {
"ResolverType": "Claim",
"ClaimType": "tenant_id"
}
}

BaseDomain is the domain the application itself is served from. A host resolves a tenant only when it is exactly one label in front of it; every other host falls back to HttpHeader.

BaseDomain is required and must be the registrable domain you own — at least two letter-digit-hyphen labels, never an address literal. Leaving it out, or setting it to something no host could be matched against, throws BaseDomainIsNotADomainName and the host does not start — UseSubdomainTenancy throws where you call it, and a value that arrives from configuration is validated while the host is starting. Neither one lets a request through to take its tenant from the client-supplied HttpHeader instead. See the subdomain resolver for the full rules.

{
"Tenancy": {
"ResolverType": "Subdomain",
"BaseDomain": "myapp.com",
"HttpHeader": "X-Custom-Tenant"
}
}
{
"Tenancy": {
"ResolverType": "Fixed",
"FixedTenantId": "acme"
}
}
{
"Tenancy": {
"ResolverType": "Development",
"DevelopmentTenantId": "local-tenant"
}
}

FixedTenantId and DevelopmentTenantId are two names for the same value, so either key configures either resolver type. Set only one of them - when a configuration source supplies both, whichever key the binder visits last wins.