Tenancy in AuthProxy
One deployment, many customers: acme.example.com and contoso.example.com hit the same proxy, and every downstream query must be scoped to the right tenant. Somebody has to answer “which tenant is this request for?” — once, reliably, at the edge. That’s AuthProxy’s tenancy system. The answer is forwarded to your services as the Tenant-ID header (see Identity for the full header set), so your application never re-derives it.
Resolution strategies
Section titled “Resolution strategies”TenantResolutions is an ordered list of strategies — AuthProxy tries each in order until one resolves a tenant:
{ "Cratis": { "AuthProxy": { "TenantResolutions": [ { "Strategy": "Host" }, { "Strategy": "Claim", "Options": { "ClaimType": "tid" } } ] } }}| Strategy | Resolves the tenant from… |
|---|---|
Host | the request host, matched against configured Domains / SourceIdentifiers |
SubHost | a subdomain convention (e.g. acme.example.com → acme) — handy for SaaS provisioning |
Claim | a claim on the authenticated user, matched against SourceIdentifiers |
Route | a regex over the request path, matched against SourceIdentifiers |
Specified | a fixed tenant id (single-tenant deployments) |
Default | a fallback tenant id when nothing else matched |
Selection | a .cratis-tenant cookie set by the built-in tenant-selection page |
Each strategy takes its own Options. Claim takes a ClaimType (defaulting to the Microsoft tenant claim, http://schemas.microsoft.com/identity/claims/tenantid); Route takes a Pattern whose regex must expose a named group called sourceIdentifier; Specified and Default take a TenantId; SubHost takes the ParentHost to strip; Selection takes a TenantsEndpoint (below).
The tenant registry
Section titled “The tenant registry”For Host, Claim, and Route, the strategy produces a source identifier which is then looked up in the Tenants registry — the tenant id is the key it maps to:
{ "Cratis": { "AuthProxy": { "TenantResolutions": [ { "Strategy": "Host" } ], "Tenants": { "acme": { "Domains": ["acme.example.com"], "SourceIdentifiers": ["acme"] }, "contoso": { "Domains": ["contoso.example.com"], "SourceIdentifiers": ["contoso"] } } } }}SubHost is the deliberate exception: the subdomain becomes the tenant id directly, with no registry lookup. That’s what makes it fit dynamically-provisioned SaaS — a new customer’s subdomain works without a configuration change. Because nothing proves such a tenant exists, pair SubHost with verification:
{ "Strategy": "SubHost", "Options": { "ParentHost": "example.com", "VerificationUrlTemplate": "https://platform.example.com/api/tenants/{tenantId}" }}Only single-segment subdomains resolve (acme.example.com → acme; foo.bar.example.com does not), and sign-in flows that bounce through a shared auth host redirect back to the tenant host afterwards — see the tenant-aware sign-in note on the Authentication page.
Letting the user pick: the Selection strategy
Section titled “Letting the user pick: the Selection strategy”Sometimes the tenant isn’t in the URL or the token at all — a consultant belongs to three tenants and has to choose. The Selection strategy handles this interactively:
{ "Strategy": "Selection", "Options": { "TenantsEndpoint": "https://platform.example.com/api/tenants/selectable" }}When an authenticated user arrives without a .cratis-tenant cookie, AuthProxy calls TenantsEndpoint (with the user’s principal attached) expecting a JSON array of { "id": "...", "name": "..." } objects:
- One tenant — it’s selected automatically; the cookie is written and the request retried.
- Several tenants — the proxy injects a short-lived
.cratis-tenantscookie with the options and serves the built-inselect-tenant.htmlpage. The page submits the choice to/.cratis/select-tenant?tenantId=<id>&returnUrl=<path>, and the proxy validates the selection against the endpoint’s response before writing the.cratis-tenantcookie.
The selection page is one of the overridable pages, so you can brand it.
Verifying the tenant exists
Section titled “Verifying the tenant exists”Optionally verify that a resolved tenant actually exists by pointing AuthProxy at a verification endpoint — {tenantId} is replaced with the resolved id, a 200 means the tenant exists, and anything else sends the user to the tenant-not-found page:
{ "Cratis": { "AuthProxy": { "TenantVerification": { "UrlTemplate": "https://platform.example.com/api/tenants/{tenantId}" } } }}A strategy-specific template (like SubHost’s VerificationUrlTemplate above) takes precedence over this global one.
When no tenant resolves
Section titled “When no tenant resolves”If strategies are configured but none resolves, AuthProxy rejects the request with 401 Unauthorized rather than forwarding it tenant-less — fail closed, not open. There are two deliberate exceptions:
- With no
TenantResolutionsconfigured, requests proceed without a tenant (single-tenant apps don’t need any of this). - With a lobby configured and
Invite.RedirectToLobbyWhenTenantUnresolvedenabled, users without a tenant are redirected to the lobby frontend instead — that’s the onboarding flow.
Strategies run in order until one answers; Host, Claim, and Route go through the tenant registry, SubHost resolves by convention for dynamic SaaS, and Selection asks the user. Verification confirms the tenant is real, and the result rides into your services as Tenant-ID. For the users who don’t have a tenant yet, continue to invites and the lobby.