Finbuckle.MultiTenant icon indicating copy to clipboard operation
Finbuckle.MultiTenant copied to clipboard

Issue with /api/register - Tenant and User Creation Flow

Open ujairkhatri opened this issue 1 year ago • 5 comments

Hello, I am implementing a very common scenario for a multi-tenant SaaS application where users can sign up by providing their email and password. The flow involves the following steps:

A tenant is created for the user upon registration. A user is created for that tenant with the provided email and password. An email verification is sent to the user to verify their account.

However, I am encountering a NullReferenceException during Step 2 when creating a user var result = await _userManager.CreateAsync(user, request.Password);

public async Task<ErrorOr<RegisterResponseDTO>> RegisterAsync(RegisterRequestDTO request)
{
    try
    {
        // Step 1 - Create Tenant
        var tenant = await _multiTenantService.CreateTenantAsync();
        if (tenant.IsError)
        {
            return tenant.Errors;
        }

        // Step 2 - Set Tenant Context
        var scope = _serviceScopeFactory.CreateScope();
        var accessor = scope.ServiceProvider.GetRequiredService<IMultiTenantContextAccessor<TenantInfo>>();
        var setter = scope.ServiceProvider.GetRequiredService<IMultiTenantContextSetter>();
        var resolver = scope.ServiceProvider.GetRequiredService<ITenantResolver>();
        var httpContextAccessor = scope.ServiceProvider.GetRequiredService<IHttpContextAccessor>();
        var httpContext = httpContextAccessor.HttpContext ?? new DefaultHttpContext();

        httpContext.Request.Headers.Append("X-Tenant", tenant.Value.Identifier);
        var mtContext = await resolver.ResolveAsync(httpContext);
        setter.MultiTenantContext = mtContext;

        // Step 3 - Create a user
        var user = new AppIdentityUser
        {
            Email = request.Email,
            UserName = request.Email,
            FirstName = request.FirstName,
            LastName = request.LastName
        
        };
        
        var result = await _userManager.CreateAsync(user, request.Password);
        .
        .
        .
        //Send verification email
        
    }
    catch (Exception ex)
    {
        return Error.Internal($"An error occurred: {ex.Message}");
    }
}

What I Need Help With: Guidance on how to properly ensure the tenant context is set up before invoking _userManager. Any suggestions on debugging why _userManager or its dependencies might be null. Best practices for handling this type of tenant and user creation flow.

Extras

services.AddMultiTenant<TenantInfo>() .WithHeaderStrategy("X-Tenant") .WithEFCoreStore<TenantDbContext, TenantInfo>();

public class TenantDbContext : EFCoreStoreDbContext<TenantInfo> { public TenantDbContext (DbContextOptions<TenantDbContext > options) : base(options) { }

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    base.OnModelCreating(modelBuilder);

    modelBuilder.ApplyConfiguration(new TenantConfiguration());
}

}

public class AppIdentityDbContext : MultiTenantIdentityDbContext<AppIdentityUser> { public AppIdentityDbContext (IMultiTenantContextAccessor multiTenantContextAccessor, DbContextOptions<AppIdentityDbContext > options) : base(multiTenantContextAccessor, options) { } public ZerpIdentityDbContext(IMultiTenantContextAccessor multiTenantContextAccessor) : base(multiTenantContextAccessor) { }

public AppIdentityDbContext (ITenantInfo tenantInfo) : base(tenantInfo)
{
    // used for the design-time factory and progammatic migrations in program.cs
    
}

protected override void OnModelCreating(ModelBuilder modelBuilder)
{

    base.OnModelCreating(modelBuilder);
    modelBuilder.ApplyConfiguration(new RoleConfiguration());
    modelBuilder.ApplyConfiguration(new UserConfiguration());
    modelBuilder.ApplyConfiguration(new UserRoleConfiguration());
    modelBuilder.ApplyConfiguration(new ClaimConfiguration());
}

}

[MultiTenant] public class AppIdentityUser : IdentityUser { public string FirstName { get; set; } public string LastName { get; set; } }

ujairkhatri avatar Nov 28 '24 17:11 ujairkhatri

Also, if someone can advise what is the best way to handle such scenario? I am fairly new to programming.

ujairkhatri avatar Nov 28 '24 17:11 ujairkhatri

Hi, there are two approaches you can go with here:

  1. after registration and tenant creation redirect to a page to finish the setup -- in the redirected request the tenant should get picked up accordingly so the UserManager gets the correct database connection.
  2. create a new DI scope from the RequestServices service provider on HttpContext, get an instance of IMultiTenantContextSetter from it, use it to set the tenant, then resolve an instance of UserManager from the new scope and it should have the correct tenant AppIdentityDbContext.

See if that helps and if you run into more issues let me know.

AndrewTriesToCode avatar Dec 07 '24 22:12 AndrewTriesToCode

This issue has been labeled inactive because it has been open 30 days with no activity. This will be closed in 7 days without further activity.

github-actions[bot] avatar Mar 21 '25 02:03 github-actions[bot]

edit: No this will not be closed--sorry about that!

AndrewTriesToCode avatar Mar 21 '25 05:03 AndrewTriesToCode

This issue has been labeled inactive because it has been open 180 days with no activity. Please consider closing this issue if no further action is needed.

github-actions[bot] avatar Sep 22 '25 02:09 github-actions[bot]