Tenancy
AuthProxy resolves a tenant ID string from each incoming request and stores it in the request context.
Downstream services receive the resolved tenant ID under both x-cratis-tenant-id and Tenant-ID.
Arc resolves its tenant from x-cratis-tenant-id by default, so an Arc application behind AuthProxy
needs no tenancy configuration. Legacy Tenant-ID forwarding is deprecated and will be removed in a
future major release; move backends to x-cratis-tenant-id.
A client cannot choose the tenant a backend sees. AuthProxy strips an inbound x-cratis-tenant-id and
the legacy Tenant-ID header, and writes back only the tenant it resolved itself under both names. Earlier
releases sent only Tenant-ID; see Upgrading.
Tenant resolution strategies
Section titled “Tenant resolution strategies”Resolution strategies run in order until one strategy resolves a tenant.
Configure them under Cratis:AuthProxy:TenantResolutions:
{ "Cratis": { "AuthProxy": { "TenantResolutions": [ { "Strategy": "Host" }, { "Strategy": "Claim", "Options": { "ClaimType": "tid" } } ] } }}Available strategies
Section titled “Available strategies”| Strategy | Description |
|---|---|
Host | Uses the request host and matches it against configured tenant Domains / SourceIdentifiers. |
Claim | Reads a claim value from the authenticated user and matches it against configured tenant SourceIdentifiers. |
Route | Extracts a source identifier from the request path by regex and matches it against configured tenant SourceIdentifiers. |
Specified | Resolves directly to a fixed tenant ID string from configuration. |
Default | Resolves directly to a fallback tenant ID string from configuration. |
SubHost | Resolves directly from subhost convention, for example acme.example.com -> acme. |
Selection | Resolves from the selected-tenant cookie set by the tenant-selection page flow. |
Claim strategy options
Section titled “Claim strategy options”{ "Strategy": "Claim", "Options": { "ClaimType": "tid" }}If ClaimType is omitted, AuthProxy falls back to reading X-MS-CLIENT-PRINCIPAL.
This is also how you resolve a tenant carried on an AuthProxy-issued client-credentials bearer token —
point ClaimType at cratis/tenant. See
Resolving a tenant from the verification response.
Route strategy options
Section titled “Route strategy options”{ "Strategy": "Route", "Options": { "Pattern": "^/tenant/(?<sourceIdentifier>[^/]+)" }}The regex must expose a named group called sourceIdentifier.
Specified strategy options
Section titled “Specified strategy options”{ "Strategy": "Specified", "Options": { "TenantId": "acme" }}Default strategy options
Section titled “Default strategy options”{ "Strategy": "Default", "Options": { "TenantId": "lobby" }}SubHost strategy options
Section titled “SubHost strategy options”{ "Strategy": "SubHost", "Options": { "ParentHost": "example.com", "VerificationUrlTemplate": "https://platform.example.com/internal/tenants/{tenantId}" }}| Property | Type | Description |
|---|---|---|
ParentHost | string | Parent host suffix used to extract the tenant ID from the request host. |
VerificationUrlTemplate | string | Optional strategy-specific verification URL template. Overrides the global TenantVerification.UrlTemplate for this strategy. |
How SubHost resolution works
Section titled “How SubHost resolution works”The strategy strips the configured ParentHost suffix from the incoming request host to derive the tenant ID.
Given ParentHost: "example.com":
| Request host | Resolved tenant ID | Notes |
|---|---|---|
acme.example.com | acme | Single-segment subhost — resolved successfully. |
contoso.example.com | contoso | Single-segment subhost — resolved successfully. |
foo.bar.example.com | — | Multi-segment subhost rejected — not resolved. |
example.com | — | No subhost present — not resolved. |
other.com | — | Host does not end with .example.com — not resolved. |
The resolved subhost string becomes the tenant ID directly.
No Tenants dictionary lookup is performed — unlike Host, Claim, and Route strategies, SubHost does not match a source identifier against a pre-configured list.
This is intentional: SubHost is designed for environments where tenants are provisioned dynamically (for example SaaS platforms where each customer gets their own subdomain).
SubHost and a shared session cookie
Section titled “SubHost and a shared session cookie”Session:CookieDomain cannot be combined with a SubHost resolution: AuthProxy refuses to start. The host-only
session cookie is what keeps a session for one tenant off the next tenant’s host, and a cookie shared across the
domain would send it to every tenant. See
Why the cookie domain is refused with SubHost tenancy.
SubHost and authentication callbacks
Section titled “SubHost and authentication callbacks”For interactive authentication flows, AuthProxy stores SubHost strategy metadata in protected authentication state when it issues the provider challenge. On callback, AuthProxy restores this metadata and redirects to the tenant subhost.
This enables a shared callback endpoint (for example auth.cratis.studio) while returning the user to the original tenant host (for example nova.cratis.studio).
Because there is no registry lookup to prove the tenant exists, you should configure VerificationUrlTemplate to have AuthProxy call your back-end to confirm the tenant is valid before forwarding the request:
{ "Strategy": "SubHost", "Options": { "ParentHost": "example.com", "VerificationUrlTemplate": "https://internal-api.example.com/tenants/{tenantId}" }}AuthProxy replaces {tenantId} with the resolved subhost value and expects a 200 response.
Any other response causes the request to be rejected with tenant-not-found.html.
See Tenant verification for full response handling details.
Selection strategy options
Section titled “Selection strategy options”{ "Strategy": "Selection", "Options": { "TenantsEndpoint": "https://platform.example.com/api/tenants/selectable" }}| Property | Type | Description |
|---|---|---|
TenantsEndpoint | string | Absolute URL for the endpoint that returns selectable tenants for the current authenticated user. Expected response shape is an array of { "id": "...", "name": "..." } objects. |
When this strategy is configured and no .cratis-tenant cookie exists yet, AuthProxy calls
TenantsEndpoint, sets the .cratis-tenants cookie, and serves select-tenant.html.
The page links back to /.cratis/select-tenant?tenantId=<id>&returnUrl=<path>, and AuthProxy validates
the selected tenant against the endpoint response before writing the .cratis-tenant cookie.
If TenantsEndpoint is unreachable, times out, returns an error other than 401/403, or returns
unparseable JSON, AuthProxy responds with 503 Service Unavailable instead of reporting that the
user has no organization. Document navigations and tenant-selection callbacks receive
service-unavailable.html; other callers receive a bare 503 without an HTML page.
An authoritative empty list or 401/403 response still proceeds to the normal no-tenant handling
(no-organization.html at 403 when no lobby is configured).
If the signed-in session no longer has a forwardable identity, AuthProxy signs the user out instead
of reporting a service outage. Navigations redirect to provider selection with reason=invalid-session;
other callers receive 401.
Revalidation of an already-selected tenant fails open during an endpoint outage, so a transient failure does not revoke an existing tenant selection.
Tenant registry
Section titled “Tenant registry”For Host, Claim, and Route, AuthProxy resolves a source identifier and then looks up the tenant ID in Cratis:AuthProxy:Tenants.
{ "Cratis": { "AuthProxy": { "Tenants": { "acme": { "Domains": ["acme.example.com"], "SourceIdentifiers": ["acme", "tenant-acme"] }, "contoso": { "Domains": ["contoso.example.com"], "SourceIdentifiers": ["contoso", "tenant-contoso"] } } } }}Lobby fallback
Section titled “Lobby fallback”When no tenant can be resolved and the lobby is configured, AuthProxy redirects the user to the lobby frontend instead of returning 401 Unauthorized.
If no lobby is configured and TenantResolutions is non-empty, AuthProxy returns 401 Unauthorized.
When TenantResolutions is empty, the request proceeds without a tenant.
Tenant verification
Section titled “Tenant verification”After tenant resolution, AuthProxy can verify that the tenant exists before forwarding the request. This is optional.
Global verification configuration
Section titled “Global verification configuration”{ "Cratis": { "AuthProxy": { "TenantVerification": { "UrlTemplate": "https://platform.example.com/api/tenants/{tenantId}" } } }}| Property | Type | Description |
|---|---|---|
UrlTemplate | string | URL template for tenant verification. Use {tenantId} placeholder. |
Response handling
Section titled “Response handling”200: tenant exists, request proceeds.404: tenant does not exist; AuthProxy servestenant-not-found.htmlwith404.- Any other status or network error: treated as tenant verification failure, and
tenant-not-found.htmlis served.
If a strategy provides a strategy-specific verification URL template (for example SubHost.Options.VerificationUrlTemplate), that template is used instead of the global TenantVerification.UrlTemplate.
Tenant not found page
Section titled “Tenant not found page”When verification fails, AuthProxy serves tenant-not-found.html.
See Error pages to override this page.
Bearer routes
Section titled “Bearer routes”On a bearer route the tenant comes from the access token alone: the claim named by
TenantClaimType (tid by default). None of the resolution strategies above run, the tenant-selection cookie is
never read and the tenant-selection page is never served.
- A token that does not carry exactly one tenant, or carries one that is not made of letters, digits,
-,.,_or~(at most 256 characters), is refused with403. There is no fallback to a default tenant. - When tenant verification is configured, the tenant is verified the same way; a tenant
that fails verification is refused with
403rather than the tenant-not-found page. - The tenant is forwarded as
Tenant-ID. It records which tenant the user chose when the token was granted; the backend still decides whether the user is a member of it.