Two mutually exclusive models are supported: Host Name based or Folder based. To give a simple example of what we mean, let's say we want two sites, one for the Physics Department and one for Chemistry, at our online University "brainiacs.com".
Host Name tenants:
https://physics.brainiacs.com/my-page
https://chemistry.brainiacs.com/my-page
Folder tenants:
https://www.brainiacs.com/physics/my-page
https://www.brainiacs.com/chemistry/my-page
You can use one or the other for a given installation. In all cases the first site created is considered the master site. In the database it has the field IsServerAdminSite set to true, and it can create new "child" sites as well as manage child site settings, users, and roles.
There is also a "Related Sites Mode" setting. When this is enabled, the users and roles attached to one site will be used by all the sites in the installation.
The users and roles and other data are tagged with a SiteId and this is used when the data is retrieved. In the case of RelatedSitesMode the users and roles are all tagged with the RelatedSiteId, but other data can still be site specific.
Host Name Sites
The tenant is resolved by the host name, ie "somedomain.com" is one host name, "www.somedomain.com" is another host name, and "www.anotherdomain.com" is another. You can map one or more host names to any given cloudscribe tenant site. Any host names that are not mapped to a specific tenant site are mapped to the default site, ie the "master" site. Typically for SEO you don't want duplicate content at different host names, so the notion of a preferred host name is also going to be supported so that if a site is handling multiple host names you can redirect all of them to the preferred one. ie you may prefer "somedomain.com" vs "www.somedomain.com" and you can redirect requests for "www.somedomain.com" to "somedomain.com" if that is your preferred host name.
For a given domain name you can create as many host name DNS "A" records as you like "host1.somedomain.com", "host2.somedomain.com", "cheeseburger.somedomain.com" or "whatever.somedomain.com".
You can create separate new sites using those host names all in a single installation of cloudscribe: you create the site and assign the host name mapping.
Of course the ip address for the host name "A" record must be the ip address where the site is running, and if it is a shared ip address then you must also have host name bindings added to the site in the web server, eg IIS (Windows Internet Information Server), so that the web server will pass the request to the correct site.
This need for creating DNS host names and host header mappings in IIS means that a technical person has to be involved when setting up a new site; non-technical people cannot easily provision a new site in this model.
Folder Name Sites
The tenant is resolved by the first folder segment in the url, except for the first site which "must" be a root level site. The fist site created during setup is considered as the "master site" and it does not have any folder mappings assigned to it and it must run as a root level site in order to resolve the child site ids correctly from the first url segment.
So the master site must work with an url like "somedomain.com/" or "www.somedomain.com/" and then child sites can be created with urls like: "somedomain.com/site2/" "somedomain.com/foo/" "somedomain.com/cheeseburger/" "somedomain.com/whatever/"
Folder based child sites are easier to provision and are very useful for sub-sites that are like sections of one site but with different authoring teams.
Configuration
This example from appsettings.json shows the current default settings. You can easily change or override these settings.
"MultiTenantOptions": {
"Mode": "FolderName",
"UseRelatedSitesMode": "false",
"RelatedSiteId": "00000000-0000-0000-0000-000000000000",
"RelatedSiteAliasId":"",
"DefaultNewUserRoles":"Authenticated Users",
"UserPerSiteWwwRoot": "true",
"UserPerSiteThemes": "true",
"UseSharedThemes": "true",
"SiteFilesFolderName": "sitefiles",
"SiteThemesFolderName":"themes",
"SiteContentFolderName":"wwwroot",
"SiteUploadFilesRootFolderName:"siteuploadfiles",
"SharedThemesFolderName":"SharedThemes",
"ThemeStaticFilesFolderName":"wwwroot"
}
Mode can be FolderName or HostName or None.
If UseRelatedSitesMode is set to true, then all sites will lookup users and roles using the RelatedSiteId, which would typically be the first site created. you can find the siteid in the site list or in the database.
Note the DefaultNewUserRoles property which has "Authenticated Users" which is a role name of a role that will be added to all users as they are created. I recommend keeping that role, but if you want to add additional roles make them semicolon separated.
The SiteFilesFolderName is the root folder where site specific themes and related static resources go. They are organized into separate sub folders per site using the site alias id ie s1, s2, etc. In early versions of cloudscribe the user upload files were also stored there, but later we added the SiteUploadFilesRootFolderName setting to separate the the user uploaded files from the theme files. Site specific uploads are organized similarly using alias id sub folders so that uploaded files are separated per site. The reason we decided to put the user uploaded files in a separate folder is to make it more docker friendly so you could mount a docker volume for the siteuploadfiles folder. You could not really do that with the themes because the layout and views in a theme need to be compiled as part of the application so it would be problematic to mount a volume for the sitefiles folder. In order not to break existing sites that already had user uploads below sitefiles folder, we kept "sitefiles" as the default value for SiteUploadFilesRootFolderName in the MultiTenantOptions class, but we override it to "siteuploadfiles" with the default appsettings in the latest project template so when new projects are created they get setup using siteuploadfiles folder.
Note that if you are using related sites mode and you want all the sites to also share the same media folder (user uploads), you can specify the RelatedSiteAliasId that matches the master site and all sites will use the same user uploads. If you don't specify this then each site will have its own separate user uploads folder. The aliasid is just a simple string like s1, s2, you can find it in site settings. It is used so we don't have to use an ugly guid (like the site id) for the folder name.
Should I Host Multiple Customer Sites as Multi-Tenant?
That is of course a business decision that is up to you. Note that the data for all tenants of a given installation are in the same database tables. Two sites managed as tenants within one cloudscribe site may use less server resource, and reduce maintenance time needed for upgrades, but on the other hand the isolation between data with different owners is less clear cut, it makes moving one site to different hosting less simple, and maintenance on one of the sites will impact on the other. So in general we see multi-tenancy as a useful thing for multiple sites owned by a single customer, and we recommend a separate installation per customer.
Managing Sites
When you first setup cloudscribe Core, the first site is created for you automatically, and this site is set in the database with IsServerAdminSite = true, making it the root administrative site. This setting allows members of the Administrators role in the site to create other sites and manage users and roles for other sites. Other sites created from the UI are not flagged as server admin sites so administrators of those sites can only manage the site they belong to.
By default users and roles are separate for each site. It is possible for the same user to register and login to multiple sites but their membership in one site does not grant them access to protected resources in other sites (with the exception of Administrators in the administrative site who as mentioned before can manage other sites, but they do this while logged into the root administrative site, they are not able to login to the other sites without creating a user account in the other sites). There are site settings for each site to enable or disable certain things. For example it is possible to disable registration, so no users can register on a site and only administrators can add users to the site.
As you can see in this model the site (aka tenant) is resolved first based on the hostname or the first folder segment of the request. The user must then log into the site or tenant to access any protected resources and their access to protected resources depends on the configured authorization policies and the user's roles and claims (within the given site) which are compared against the requirements of the policy. The authorization policies are configured in the Startup.cs of the web application, where they can be customized. So for example there is a policy protecting the administrative area in cloudscribe Core, it requires the user to be in the Administrators role. While each site will have a role named Administrators, the user must be in the role for the site resolved from the request. Members of the Administrators role of one site are not administrators of other sites, even though other sites also have a role with that name. A user could have a separate account in each site with the same email and they could be added to the Administrators role of each site if you want to make them administrators but users and roles are not shared across sites unless the system is configured with related sites mode. For more information on defining custom authorization policies see the documentation here.
When you create multiple sites with cloudscribe core there is nothing protected by default except the administrative area in each site. If you have a public home controller that allows anonymous access then users can still browse the home page of any site. If you wanted to prevent that then you would have to define a policy to protect the home page controller, then you could decorate the controller class or specific controller actions that you want to protect with a policy attribute:
[Authorize(Policy = "HomePagePolicy")]
You would have to define the policy with that name in Startup.cs something like this:
services.AddAuthorization(options =>
{
options.AddPolicy(
"HomePagePolicy",
authBuilder =>
{
authBuilder.RequireRole("Administrators", "Content Administrators");
});
// add more policies here as needed
});
If instead of using a standard home controller, you use cloudscribe.SimpleContent, it allows you to create virtual pages and each virtual page has a setting for "AllowedViewRoles". The setting is empty by default, but if you add one or more roles to it then the page would be protected by the required view roles and will not be accessible to anyone who is not a member of the role. The page would also be automatically filtered out of the menu in this case for users who are not members of the needed roles.
What about a user per tenant solution?
This question comes up from time to time: it seems some people want to be able to resolve the user first and then determine the tenant based on the user. Sorry but that is not how it works in cloudscribe Core. What would be possible is to have tenants defined that correspond to users, but then in the root administrative site you would need a custom feature to let the user enter his email address, then you could lookup the tenant in a custom table and redirect the user to the url of the tenant. So you would need a master list in the database to relate the email address of the user to a tenant.
What about a database per tenant solution?
That wasn't the goal for cloudscribe Core: the main aim is to make it easy to provision new sites. A database-per-tenant solution would require manual steps to add new tenants, such as creating a new database and database user and keeping a list of the tenants and their database connection strings in a central master database. We don't have any plans to implement that kind of a solution near term, but we are not ruling out doing that in the future. We are also interested in providing a way to import and export tenant data to facilitate moving a tenant to another installation or database. We don't have a timeline for implementing that, but the interim solution is that if we need to provide a copy of a single tenant we can make a copy of the entire site database, and delete the other tenant data in the copy, leaving just the data of the required single tenant. As discussed above, we recommend an installation per customer, so if there are multiple tenants in the installation, they are tenants that all belong to one customer and we don't often need to separate the data later.