How to architect a Multi-tenant application?
Being a SAAS (Software as a Service) based application, we believe multi-tenancy and security is one of the primary concern. We ensure that data from one client is completely isolated from another such that any customization we made to our platform or even done in any other client is not affecting another.
What is Multi-tenancy?
It is a concept in which a single instance of software application (as APPSeCONNECT) serves multiple customers such that customizing one part of solution for a particular customer or tenant does not affect other tenant. Multi-Tenancy is a concept specific to cloud based solution which can be achieved either by giving complete isolation in database or managing the complexity in the application itself. When database isolation is considered, we can partition data based on tenant id associated with one tenant such that no data particular to a tenant can be accessible to another tenant.
While accessing our services, any request made to our server is first validated using the unique tenant id (a globally unique identifier) and then it performs authorization. Let us look into more details on how the platform is developed:
In APPSeCONNECT we use Cloud infrastructure to build our services. In the above image, you can see how our architecture is laid out, where the database layer is built using schemas per instance, and cloud application uses the instance of the same tenant to connect the application. Let us delve deep into the architecture so that you can understand the underlying architecture in which APPSeCONNECT is built in.
Stage 1 Isolation (using separate schema for data separation):
For our business, isolation of data is important, and we care for it the utmost. To do this, we have introduced database level isolation. As we don’t want to physically separate the data into a number of databases, we consider using a feature of SQL server on Schema based data separation. To do this, we wanted to use the same technique in which large scale databases are built with. We create few tables which are common for every organization, while we create separate tables for each of the organizations. Let us consider, how to create database level isolation for an application:
-
- Create database to centralize database configurations. As we are using SQL Server, we use the following script to create a database.Create database APPSeCONNECT
ON (Name = appseconnect_db, FileName = ‘drive\appseconnect.mdf’, size=10,maxsize=500, filegrowth=5) Log ON (Name = appseconnect_log, FileName = ’drive\appseconnect.ldf’, size= 5MB, MaxSize = 25MB, FileGrowth=5MB); GO
After creating the database, we can create the common tables. For instance, let’s say APPSeCONNECT keeps track of Subscriptions and expiry, so we have a table commonly in dbo schema (the default schema) which keeps track of data common for any tenant.
- Each of the tenant will have their own schema, and all the transactional tables associated with that particular organization. The schema specific to that tenant will be only accessible to the user associated with the schema.
- Create database to centralize database configurations. As we are using SQL Server, we use the following script to create a database.Create database APPSeCONNECT
Hence if we think the database is APPSeCONNECT, we create three separate schemas for each of our tenant. There will also be some tables which are associated with [dbo] which connects all other tenants.
- To create a schema for a particular user, you first need to create a user on the database. To create the user, we use the following :
CREATE USER ParrotUser WITH PASSWORD = ‘aaaaafafddf@34344’
The user and the password is stored on the common tables such that only we can keep track of the user details.
- Let us create a schema for that user ParrotUser. Let’s say, the user is Parrot. So in our APPSeCONNECT website, we are going to have a schema defined as under :CREATE SCHEMA Parrot AUTHORIZATION ParrotUser.
- Once the schema is ready, the user ParrotUser can have access to only access to the tables specific to that particular tenant. But to grant access, we need to give permission to select on common tables:
GRANT SELECT ON Subscription TO Parrot User;
The above statement will give permission to see the data for a table subscription.
Now after the schema is ready, every registration of new tenant will automatically execute a process, which will create all these, preferably using a procedure. APPSeCONNECT uses secured channel to communicate login or other sensitive details always to ensure your data is secure in cloud.
Stage 2 Isolation (using MVC in Web):
MVC or model-view-controller is an architecture best suited for multi-tenant environment. It is a flexible architecture where all the concerns are separated with one specific problem to solve. The controller acts as a mediator between View and Model. Model is helping to load data for a request while the view is for display purpose. In ASP.NET, Razor syntax is used to create the views, the controller selects the view after authorizing a request and creates a response.
To deal with multi-tenant application in MVC application, you need to handle separate authorization. In ASP.NET this can be handled using “AuthorizeAttribute”.
public class AuthorizeRolesAttribute: AuthorizeAttribute
{
private bool IsAuthorized;
public AuthorizeRolesAttribute()
: base()
{
this.IsAuthorized = false;
}
protected override bool AuthorizeCore ( System.Web.HttpContextBase httpContext)
{
if ( !httpContext.User. Identity.IsAuthenticated)
this.IsAuthorized = false;
else
{
//Check roles
handler= new TenantIdentifierHandler (httpContext);
this.IsAuthorized = handler.HasRequiredToken (controller, action);
}
return this.IsAuthorized;
}
protected override void HandleUnauthorizedRequest (AuthorizationContext filterContext)
{
if ( !filterContext.HttpContext .User.Identity. IsAuthenticated)
{
//Here login page is called
base.HandleUnauthorizedRequest (filterContext);
}
else
{
if (!this.IsAuthorized)
{
var tempData = new TempDataDictionary();
tempData.Add("ErrorReason", "Unauthorized");
filterContext.Result = new ViewResult() { ViewName = "Error", TempData = tempData };
}
}
}
}
The above code will find whether the tenant is authorized and depending on the same, it creates a session id and keeps in the server. When using this AuthorizationAttribute in the controller, the security checks are automatically performed and every request if not marked as AllowAnonymous will be authorized.
Once the data is authorized, the View is generated based on that particular tenant id, and the model is prepared for the MVC application for that particular tenant.
[AuthorizeRoles]
public class AppsController : DbControllerBase
{
private APPSeCONNECTDb db = null;
public AppsController()
{
this.db = this.DatabaseFactory.DataFactory;
}
//
// GET: /Apps/
public ActionResult Index()
{
var model = this.db.GetApps();
return View(model);
}
}
So for the controller, we automatically ensure the data is validated and roles are checked for the request.
Our Data layer (APPSeCONNECTDb) creates a separate layer for every tenant and the DatabaseFactory object automatically points to the database schema where
The image depicts how the applications are getting connected to the database and how the data is getting retrieved to and from the respective tenant based schema.
Conclusion
Multi-tenancy is a common problem nowadays. With the boom in cloud technology, the expectation from a cloud based provider is huge. Building the correct architecture for a solution is essential. Depending on your requirements, you can either way use our technique, which is having medium investment but maximum security.
APPSeCONNECT is a hybrid integration solution which uses the best of both on-premise and cloud technology to ensure 100% data security.