Skip to content

Implementing Shared Login using APIs, with identification of tenancy after login #929

@GorgonSlayer

Description

@GorgonSlayer

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.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions