Add Multi-Tenancy to Asp.Net Core Identity

Now I think it starts to get a bit more interesting.  I would like my API to be able to handle Multi-Tenancy.  For this situation we need to add a whole new class/table to Asp.Net Core Identity.  It is not as bad as it sounds.

Create the Tenant

Let’s start by creating a Tenant object.

And let’s look at that new Tenant class.

In the Table attribute, I named the table “AspNetTenants” to go along with the Asp.Net Core Identity nomenclature that is already in place.  I set the Id property to be the Primary Key when the table is created.  I chose to keep using the GUID for the Id just like the other Identity tables.  Some people have converted that all over to integers but I am just going to stay consistent here.

Tie Tenant to User

If you look to the bottom of the new Tenant class, you will notice a reference back to our custom IdentityUser object (ApothecaricUser).

A user must be tied to a Tenant here so we know who the user belongs to.

In order to tie the User over to this new Tenant entity, we need to go back and update our ApothercaricUser class.

Let’s add the following to our ApothercaricUser class to reference our Tenant

The TenantId will add the “TenantId” field to our database and the “Tenant” will tie the user to a Tenant in code.  Our ApothecaricUser class should now look like this:

Let EF and the database know!

In order to have EF and the database know about our Tenant, we will need to make our Db Context aware of the Tenant.  We need to add the Tenant object to the ApothecaricDbContext class.  When we access this table through EF, we want to refer to it simply as “Tenants”.

Here we add a DbSet typed to the Tenant class and name that property “Tenants”.  Now EF will know how to interact with the Tenant object and also allow the Db Migrations to add that new table to our database.

Add Some Indexes using Fluent Api Syntax

To look back at the Tenant class again, there are two ways we can match up an Api Request to a Tenant.  First is to use the Domain name that comes in on the http request.  The other is to put a code in the URL of any request send to the Api, such as “http://somecompany.com/…/apothecaric/api/login/scca” or something along those lines.  I have chosen to work with the Domain name.  A better explanation of how to accomplish using Domains Names for Multi-Tenancy is outlined here.  I want to make sure that both the Code and the DomainName properties/fields on the Tenant class/table are unique.  We will need to make sure both of these fields have a Unique Index added to them.  To make this happen, we need to go back into our ApothecaricDbContext and add an override method to the base class’ OnModelCreating method.  Here we will use the Fluent Api syntax and make reference to our Tenant class and its two Properties that we need to add the unique index on, Code, and DomainName.

More Migrations

We will then need to run the database Migrations again.

 

And A Look At The Database

When we refresh the database, we see our new AspNetTentants table.

Also, looking at the AspNetUsers table, we can also find our foreign key back to our AspNetTenants table.

What’s Ahead

Next set, lets look at adding some controller endpoints to Register a new user and get a Jwt Token back.  All with capturing the Tenant on the requests!

Hope to see you then!

10 Comments

  1. Hello, did you ever finish the next part of this article.

    I am using it to set up my multi-tenancy and I was wondering how you got past the UserManager.CreateAsync(identityuser, password)? It is not allowing me to create another user (same email but different domain).

    Any assistance is appreciated

    1. Hi Allen,

      Yes, I have a few more articles on this blog that work the rest of the way through this small example app. Although I have not made changes to allow a user register with an email that is already used but having a different domain, this should not be too much of an issue.

      If you look at the “Register A New User In Asp.Net Core Web Api” article, in the “Register Middleware” section, you see on line 10 “cfg.User.RequireUniqueEmail = true;” To start, change this setting to “false”. That way we can at least have email addresses that are the same.

      Now, I have Asp.Net Identity to also use the user’s email address as the user’s name. To get around this limitation, I would look at the answer that Tony Cobb has on Stack Overflow. (https://stackoverflow.com/questions/29094063/how-to-allow-user-to-register-with-duplicate-username-using-identity-framework-1). This is exactly what I would have done to work around that limitation.

      Fire back if you have more questions!

      Eric

  2. Hello, Your code seems to be working but only with localhost. When I deploy my app to the server with real domain name, it doesn’t work. I get error : abc.mydomain.com is currently unable to handle this request. HTTP ERROR 500.

    There is something wrong with this line in TenantProviderMidleware:

    urlHost = urlHost.Remove(urlHost.IndexOf(“:”), urlHost.Length – urlHost.IndexOf(“:”)).ToLower().Trim();

    How showld be configured the domain name ?
    In the database, the DomainName should be just abc? or abc.mydomain.com ?

    Please I need to implement this urgent.

    1. Hi Joel,

      If you look at this line:

      string urlHost = httpContext.Request.Host.ToString();

      C#’s HttpContext.Request.Host would return “abc.mydomain.com” in your case. The domain name in the database should then be “abc.mydomain.com”, unless you want to further pick the host apart and get either the subdomain (“abc”), etc.

      I have added a check:

      if(urlHost.IndexOf(“:”) != -1)

      before the line:

      urlHost = urlHost.Remove(urlHost.IndexOf(“:”), urlHost.Length – urlHost.IndexOf(“:”)).ToLower().Trim();

      for use in development. In production, you should not hit this “Remove” part then since we won’t be using ports.

      Let me know if this helps.

      Best of luck!
      Eric

      1. Hi Eric,

        Thanks for your answer. Your code help me. Everything was working in development. I change to production and now I’m getting an error: “Invalid object name ‘AspNetTenants'”. The table is in the database, I can see it in the server. But the app doesn’t launch.

        I tried running migration again and updating the database, but the same error is showing up. I double cheked the code, evrything is in place.

        Any idea what can be the problem?

  3. Hi Eric,

    Thanks for your answer. Your code help me. Everything was working in development. I change to production and now I’m getting an error: “Invalid object name ‘AspNetTenants'”. The table is in the database, I can see it in the server. But the app doesn’t launch.

    I tried running migration again and updating the database, but the same error is showing up. I double checked the code, everything is in place.

    Any idea what can be the problem?

    1. Hi Joel,

      Are you using the Seeder functionality on your production database also?

      using (var scope = app.ApplicationServices.CreateScope())
      {
      var seeder = scope.ServiceProvider.GetService();
      seeder.Seed();
      }

      The first item is wants to create is a Tenant and thus, will insert into the AspNetTenants table. This will tell us why you are hitting this particular table first and getting the error you are seeing and not just some other table.

      I would make sure your connection string is going after the correct server and database. Then I would see if the user you are connecting to the database under, has rights to that table. You may need to make sql administrator adjustments to the tables.

      That would be where I would start to look.

      -Eric

      1. I wasn’t using the Seeder functionality. I solved the error changing the connection string.

        In my startup file I had:

        services.AddDbContext(x => x.UseSqlServer(Configuration.GetConnectionString(“DefaultConnection”)));

        I changed it to:

        services.AddDbContext(x => x.UseSqlServer(“HERE MY WHOLE CONNECTION STRING FROM AZURE”));

        Now is working. Thanks for your help.

  4. Hi Eric,

    As I’m learning from your articles, i would like to know if the next steps’ article is also available.
    If possible, let me know where i can find it.

    Thanks!

Leave a Reply

Your email address will not be published. Required fields are marked *