-
-
Notifications
You must be signed in to change notification settings - Fork 288
Description
I am currently attempting to build a multitenant system where a single sign in page (provided by a SPA), is able to perform all the functions of ASP.Net Core Identity. I have some customs requirements due to providing functionality for a mobile app and desktop app. For context, the application is a shift scheduling and time management system.
Requirements:
- Users need to be able to access multiple tenants after login, selecting the individual tenant preferably. (Default if there is only one tenant connected to the account.
- A subset of users must be able to self register (administrative users) and a subset must be invited to use the system.
- Invited Users need to able to switch between tenants if necessary. (think of switching between employers if a user has two roles at two different firms which both use the system for managing their shifts)
- Certain DbContext operations need to check for data across multiple tenants. (think comparing shifts assigned to users and user availability)
Data Model context:
Users are connected to Tenants (Organisations) via an Employee class. Organisations create an Employee class. Users have a One to Many relationship with Employees.
Below is some of the implemented code:
public class ClockworkDbContext : IdentityDbContext<ApplicationUser, ApplicationRole, Guid>, IMultiTenantDbContext
{
public DbSet<Organisation> Organisations { get; set; }
public DbSet<Employee> Employees { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
//Create the Entities first
base.OnModelCreating(modelBuilder);
//Enforcing Multitenancy
modelBuilder.ConfigureMultiTenant();
}
public override int SaveChanges(bool acceptAllChangesOnSuccess)
{
this.EnforceMultiTenant();
return base.SaveChanges(acceptAllChangesOnSuccess);
}
public override async Task<int> SaveChangesAsync(bool acceptAllChangesOnSuccess,
CancellationToken cancellationToken = default(CancellationToken))
{
this.EnforceMultiTenant();
return await base.SaveChangesAsync(acceptAllChangesOnSuccess, cancellationToken);
}
public ITenantInfo? TenantInfo { get; }
public TenantMismatchMode TenantMismatchMode { get; }
public TenantNotSetMode TenantNotSetMode { get; }
}MultiTenantStoreDbContext:
public class MultiTenantStoreDbContext : EFCoreStoreDbContext<Organisation>
{
public MultiTenantStoreDbContext(DbContextOptions options) : base(options)
{
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
var configuration = System.Configuration.ConfigurationManager.AppSettings;
optionsBuilder.UseNpgsql(configuration["DefaultConnection"]);
base.OnConfiguring(optionsBuilder);
}
}ApplicationUser class:
public class ApplicationUser : IdentityUser<Guid>
{
public DateOnly DateOfBirth { get; set; }//Verification of birthday for identity that this is the correct user.
}Program.cs
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddIdentityApiEndpoints<ApplicationUser>()
.AddEntityFrameworkStores<ClockworkDbContext>();
builder.Services.AddMultiTenant<Organisation>()
.WithClaimStrategy()
.WithDistributedCacheStore(TimeSpan.FromHours(72))
.WithEFCoreStore<MultiTenantStoreDbContext, Organisation>();
var app = builder.Build();
app.MapIdentityApi<ApplicationUser>();
app.UseAuthentication();At present, when I generate the ApplicationUser class, it is always bound to an Organisation via the Multitenancy system. What I would like to know is what I am doing wrong. I have looked through the past issues, but very few go into details about implementing Multitenancy in this fashion.