We have a multi-tenant ASP.NET application. So far tenants have been isolated from each other, however now we have Agencies that manage multiple tenants and want to be able to manage all their tenants with a single user account. I'm trying to figure out the best way to accomplish that, hopefully without much change to the existing technologies that we are using.
Relevant technical details:
- AspNetSqlMembershipProvider for both membership and roles
- C# 4.0 (soon to be 4.5)
- Forms Authentication
- Both aspx and MVC (v3) pages
- Assume 100 or more tenants, so any solution needs to support that
I believe the requirements are very similar to the security model for SQL Server. We have one set of logins which represent all the users that can login to the system. Users should be able to be given roles to one or more databases (tenants). Example: User Bob has admin role in company A, but only user role in company B. We also have a "sysadmin" role for my company's employees which allow us access to any tenant as well as specialized administrative privileges such as create/delete tenants, etc.
I've done a lot of research into various libraries, frameworks, etc, and I haven't found any convincing evidence that some other library or framework will be better than what we currently have. So I'm currently thinking of just figuring out how to make Sql Membership provider do what I want, unless someone can point me in a better direction. I'm also not sure I know the best terms to search for in this.
I've got 2 options I'm considering:
- Add only a handful of roles to the membership provider and handle all the questions of "does the current user have this role in this tenant" outside of membership provider. Membership provider would be used to handle basic access to the system.
- Add tenant specific roles to membership provider. We would have (# of roles) x (# of tenants) total roles in the system. Each new tenant would add another set of roles to the system, e.g. "Tenant A:Admin", "Tenant A:User", etc. Would need some additional tables to manage the relations as well as probably some custom code to ensure that access is requesting the correct tenant-specific role from the membership provider.
Are either of these options good? Or should I be looking elsewhere for support for this?
I don't think you are going to be able to shoehorn multitenancy into any out of the box role provider, so you might as well keep using SqlMembershipProvider (and SqlRoleProvider). Even the newest Microsoft.AspNet.Identity still assumes a vanilla many-to-many between users and roles. What you really need is to add a 3rd column to the primary key of that many-to-many table, which will id your tenant, i.e.:
...with this, you are able to have users with different privileges for different tenancies, all using the same set of role names.
If you went with option #2, then your [Authorize] attributes would explode. Imagine this:
... all of those attributes would have to be written at compile time unless you went with a custom AuthorizeAttribute, which you could do. But even then you are left creating a new set of roles each time you add a tenant to the system, which should not be necessary.
I work on a big multi-tenancy application. We came to the conclusion that it was easier to maintain separate databases per tenant, and have the web application automatically switch database contexts, rather than try and use an over-complicated database schema to model different tenants.
The benefits
The drawbacks
Implementation using multiple databases
I have also developed a multi-tenant application that uses a single database. You quite quickly have problems making sure that you don't cross tenant data. Every query needs to include a tenant id filter. The database queries are therefore always slower as a result, although you can index everything you can to try and improve the situation.
With regards to the Membership question, you can install the membership schema into each tenant database.
What doesn't work
The ideal alternative would be to dynamically switch the ApplicationName, but although it seems to work, ApplicationName is not thread safe, therefore this would not be reliable:
Alternative: MembershipReboot
Multi-tenancy is hard in .Net. An open source alternative to using the built in Membership is to use MembershipReboot, written by Brock Allen. It has some excellent features including multi-tenant support out-of-the-box:
Alternative: ServiceStack REST
Another alternative if you are building modern web applications that heavily use JavaScript MVC frameworks such as AngularJS, EmberJS or BackboneJS is to use ServiceStack REST services. ServiceStack has a long list of Authentication features, and from my experience of SS, I find it has an extremely well thought out API model.