Tenant identification
Tenant identification refers to the process of identifying tenants in HTTP requests.
This is generally done using middleware. Out of the box, our package supports:
- Domain identificaton: identifying tenants by full hostnames
- Subdomain identification: identifying tenanats by subdomains of central domains
- Combined domain and subdomain identification: both of the above
- Path identification: identifying tenants by a
/{tenant}/
path parameter - Request data identification: identifying tenants by headers, query parameters, and cookies
- Origin header identification: domain identification using the
Origin
header, essentially a hybrid between domain and request data identification
These middleware identify the tenant from the current request and subsequently initialize tenancy.
Domain identification
The InitializeTenancyByDomain
middleware identifies the tenant using the request hostname, i.e. the full domain including
any amount of subdomains:
Domain-based identification middleware should always be used with the PreventAccessFromUnwantedDomains
middleware.
If you’re using universal routes and the request is made on a central domain, the request will be handled without tenancy initialization — it will run in the central context.
Subdomain identification
The InitializeTenancyBySubdomain
middleware works the same as the domain identification middleware, except it checks for
subdomains instead of full hostnames. These subdomains must be subdomains of configured central domains
(tenancy.identification.central_domains
config):
Combined domain and subdomain identification
The InitializeTenancyByDomainOrSubdomain
middleware combines the two approaches above:
- If the request hostname ends with any of the configured central domains, the middleware will use subdomain identification
- If the request hostname doesn’t end with a central domain, the middleware will use domain identification
Path identification
The InitializeTenancyByPath
middleware uses the {tenant}
parameter in routes to identify the current tenant and initialize
tenancy. Unlike other bootstrappers, this is the only one that requires that you register routes differently — you need to
include the {tenant}
parameter as part of the route path.
The parameter is not passed to the controller, it is dropped automatically by the package after the tenant is identified.
The name of the route parameter is configurable via the tenant_parameter_name
config:
If you’d like to use different values than tenant keys for the route parameter, you can change the
tenant_model_column
config:
This can be useful if you want clean URLs for your tenant routes, but want to keep the security of using random strings for tenant keys.
You can also specify the column using the binding field syntax:
When using the binding field syntax, you need to whitelist the columns you use in these route definitions:
Request data identification
The InitializeTenancyByRequestData
middleware supports identifying tenants using:
- headers (
X-Tenant
by default) - query parameters (
?tenant=
by default) - cookies (
tenant
by default)
All of these approaches use the tenant key, the column is not configurable. You may however configure the names of all these:
Usage:
Origin header identification
The InitializeTenancyByOriginHeader
middleware works like the InitializeTenancyByDomain
middleware, except that it reads
the domain from the Origin
header instead of the request hostname.
The use case for this is when you have an API deployed on say api.yourapp.com
and SPA frontends served from client domains:
tenant1.com
yourapp.tenant2.com
Browsers will automatically add the Origin
header to any request made from the frontend. For example:
Notice that the tenant is not being specified anywhere. However, if you dd($request->header('Origin'))
on the backend, you get:
The middleware uses this browser feature to make tenant identification from static SPA frontends extremely easy. You don’t have to obtain the tenant id anywhere, the package will know what tenant the request is meant for based on the domain it’s coming from.
To use this middleware, simply make sure you use the HasDomains
trait on your Tenant model and assign each tenant a domain
matching the site where their frontend is deployed.
Tenant resolvers
Under the hood, all of these middleware use tenant resolvers. Tenant resolvers are classes that receive some primitive input from the identification middleware and handle the rest of tenant identification.
The benefits of moving parts of the logic into resolvers are:
- caching + cache invalidation,
- code reuse: multiple identification middleware may use the same resolver. The package ships with 7 identification middleware but only 3 resolvers.
The caching logic specifically ensures that, in production, a connection to the central database doesn’t have to be established to fetch the tenant — since establishing connections can add a bit of latency in some setups. Instead, the tenant model is read from cache.
As for invalidation: when a tenant is updated, the cache for that tenant is pruned in all resolvers.
The cache logic can be configured in the tenancy.identification.resolvers
config:
Customizing onFail logic
If the middleware doesn’t manage to identify a tenant, it will abort the request. Out of the box, this means throwing a
TenantCouldNotBeIdentifiedException
exception. This behavior can be customized by setting the $onFail
static property:
Each identification middleware can have a different onFail
handler. In other words, you need to configure this property
for each identification middleware you use separately.
Alternatively, you can configure your exception handler to render all InitializeTenancyByDomain
exceptions as e.g. 404s.
If you want your requests to be handled centrally when a tenant cannot be identified, see the Universal routes page of the documentation.