Tenancy

July 10, 2026 · View on GitHub

AuthProxy resolves a tenant ID string from each incoming request and stores it in the request context. Downstream services receive the resolved tenant ID via the Tenant-ID header.


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

StrategyDescription
HostUses the request host and matches it against configured tenant Domains / SourceIdentifiers.
ClaimReads a claim value from the authenticated user and matches it against configured tenant SourceIdentifiers.
RouteExtracts a source identifier from the request path by regex and matches it against configured tenant SourceIdentifiers.
SpecifiedResolves directly to a fixed tenant ID string from configuration.
DefaultResolves directly to a fallback tenant ID string from configuration.
SubHostResolves directly from subhost convention, for example acme.example.com -> acme.
SelectionResolves from the selected-tenant cookie set by the tenant-selection page flow.

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

{
  "Strategy": "Route",
  "Options": {
    "Pattern": "^/tenant/(?<sourceIdentifier>[^/]+)"
  }
}

The regex must expose a named group called sourceIdentifier.


Specified strategy options

{
  "Strategy": "Specified",
  "Options": {
    "TenantId": "acme"
  }
}

Default strategy options

{
  "Strategy": "Default",
  "Options": {
    "TenantId": "lobby"
  }
}

SubHost strategy options

{
  "Strategy": "SubHost",
  "Options": {
    "ParentHost": "example.com",
    "VerificationUrlTemplate": "https://platform.example.com/internal/tenants/{tenantId}"
  }
}
PropertyTypeDescription
ParentHoststringParent host suffix used to extract the tenant ID from the request host.
VerificationUrlTemplatestringOptional strategy-specific verification URL template. Overrides the global TenantVerification.UrlTemplate for this strategy.

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 hostResolved tenant IDNotes
acme.example.comacmeSingle-segment subhost — resolved successfully.
contoso.example.comcontosoSingle-segment subhost — resolved successfully.
foo.bar.example.comMulti-segment subhost rejected — not resolved.
example.comNo subhost present — not resolved.
other.comHost 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 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

{
  "Strategy": "Selection",
  "Options": {
    "TenantsEndpoint": "https://platform.example.com/api/tenants/selectable"
  }
}
PropertyTypeDescription
TenantsEndpointstringAbsolute 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.


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

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

After tenant resolution, AuthProxy can verify that the tenant exists before forwarding the request. This is optional.

Global verification configuration

{
  "Cratis": {
    "AuthProxy": {
      "TenantVerification": {
        "UrlTemplate": "https://platform.example.com/api/tenants/{tenantId}"
      }
    }
  }
}
PropertyTypeDescription
UrlTemplatestringURL template for tenant verification. Use {tenantId} placeholder.

Response handling

  • 200: tenant exists, request proceeds.
  • 404: tenant does not exist; AuthProxy serves tenant-not-found.html with 404.
  • Any other status or network error: treated as tenant verification failure, and tenant-not-found.html is 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

When verification fails, AuthProxy serves tenant-not-found.html. See Error pages to override this page.