OK, this is going to be quite a big and open question(s)
I want to extend Identity Server 4 and Microsoft Identity to be used for multi-tenancy applications.
This is what I've done so far...
Extended the Identity Role, in this example, I've added a description property
public class CustomRole : IdentityRole
{
public CustomRole(string roleName, string description) : base(roleName)
{
Description = description;
}
public string Description { get; set; }
}
Extended the Identity User
public class CustomUser : IdentityUser
{
public string FirstName { get; set; }
public string Surname { get; set; }
public int TenantId { get; set; }
}
I then have a Tenant entity
public class Tenant
{
public int Id { get; set; }
public string Name { get; set; }
public string TenantCode { get; set; }
... plus extra fields
}
Extended the SignInManager class to use a new tenant lookup service
public class ApplicationSignInManager<TUser> : SignInManager<TUser> where TUser : CustomUser
{
private readonly UserManager<TUser> userManager;
private readonly ITenantService tenantService;
public ApplicationSignInManager(UserManager<TUser> userManager,
IHttpContextAccessor contextAccessor,
IUserClaimsPrincipalFactory<TUser> claimsFactory,
IOptions<IdentityOptions> optionsAccessor,
ILogger<SignInManager<TUser>> logger,
IAuthenticationSchemeProvider schemes,
IUserConfirmation<TUser> confirmation,
ITenantService tenantService) :
base(userManager,
contextAccessor,
claimsFactory,
optionsAccessor,
logger,
schemes,
confirmation)
{
this.userManager = userManager ?? throw new ArgumentNullException(nameof(userManager));
this.tenantService = tenantService ?? throw new ArgumentNullException(nameof(tenantService));
}
public async Task<SignInResult> PasswordSignInAsync(string tenantCode, string userName, string password, bool isPersistent, bool lockoutOnFailure)
{
var tenant = await tenantService.GetByCodeAsync(tenantCode);
if (tenant.HasNoValue)
return SignInResult.Failed;
var user = await userManager.FindByNameAsync(userName);
if (user == null)
return SignInResult.Failed;
if (user.TenantId == tenant.Value.Id)
return await PasswordSignInAsync(userName, password, isPersistent, lockoutOnFailure);
else
return SignInResult.Failed;
}
}
I then register for the new classes in the program class
builder.Services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));
builder.Services.AddIdentity<CustomUser, CustomRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddSignInManager<ApplicationSignInManager<CustomUser>>()
.AddDefaultTokenProviders();
Now, this all works fine, but...
Here are my questions:
How can this work with different applications that are multi-tenancy? e.g. What if application 1 has a tenant record like above but application 2 needs to have an address or some other option saved against the tenant?
Similar question with the users. What if application 2 needs to store an age or old address value for example against the user?
What about when the delegation of creating the users is up to the application itself? i.e. One of the tenants wants to create users for access to their part of the system
With the above questions, should the Identity Server be split into one instance per application to allow for different options, or should different tables/databases be created for each application? e.g. Application 1 has its own tenant and user table. I'm not sure which way to go. Or is there a much better approach?
I've searched the internet for examples and I only find very similar ways to what I've already done. Any help or examples would be greatly appreciated.
Thanks