Recently Spatie released a brand new package for multi-tenancy called laravel-multitenancy.
It comes with great support to work out of the box with sub-domains like,
https://zluck.infychat.com
https://infyom.infychat.com
https://vasundhara.infychat.com
It identifies the tenant based on the sub-domain and sets a database runtime for your tenant-specific models.
Recently, we used it in one of our clients for Snow Removal CRM. Here we have two models,
- Freemium Model – with no sub-domain (application will work on main domain only)
- Premium Model – where the tenant will get its subdomain
And a user can convert his account from Freemium to Premium at any point in time by just subscribing to the plan.
So what we want is, on the backend, we want to have a separate database for each tenant, but the application should run on main as well as a sub-domain.
So what we want to have is the ability to extend/customize the tenant detection mechanism. And Spatie does a very good job there where you can customize the logic.
You can create your own TenantFinder class and configure it in the config file of the package. And there is very good documentation for that here:https://docs.spatie.be/laravel-multitenancy/v1/installation/determining-current-tenant/
To do that, what we did is, we have a field called tenant_id in our users table. All of our users are stored in the main database since we may have user access across the tenant.
And when any user does a login, we listen for the event Illuminate\Auth\Events\Login which is documented in Laravel Docs over here.
When a user does a login, our listener will be called and will create a cookie called tenant on the browser with the tenant id of the user. So our listener will look like,
user;
Cookie::forget('tenant');
Cookie::queue(Cookie::forever('tenant', encrypt($authUser->tenant_id)));
}
}
Also, we encrypt the cookie on our end, so we do not want Laravel to encrypt it again, so we added the tenant cookie into except array of EncryptCookies middleware as per documentation here. so our middleware looks like,
Now at the last point, we extended our logic to find a tenant and get it to work on the main domain as well as sub-domain.
We have created our own custom class called InfyChatTenantFinder, which looks like,
getHost();
list($subDomain) = explode('.', $host, 2);
// Get Tenant by subdomain if it's on subdomain
if (!in_array($subDomain, ["www", "admin", "infychat"])) {
return $this->getTenantModel()::whereDomain($host)->first();
}
// Get Tenant from user's account id if it's main domain
if (in_array($subDomain, ["www", "infychat"])) {
if ($request->hasCookie('tenant')) {
$accountId = $request->cookie('tenant');
$accountId = decrypt($accountId);
$account = $this->getTenantModel()::find($accountId);
if (!empty($account)) {
return $account;
}
\Cookie::forget('tenant');
}
}
return null;
}
}
So basically, first we check if the sub-domain is there, then find a tenant from the sub-domain.
If the domain is the main domain then get the tenant id from the cookie and return the account (tenant) model.
So this is how you can customize the logic the way you want to have a custom tenant identification system.