Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
ujairkhatri opened this issue Nov 28, 2024 · 2 comments
Open

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

ujairkhatri opened this issue Nov 28, 2024 · 2 comments
Labels

Comments

@ujairkhatri
Copy link

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()
.WithHeaderStrategy("X-Tenant")
.WithEFCoreStore<TenantDbContext, TenantInfo>();

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

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

    modelBuilder.ApplyConfiguration(new TenantConfiguration());
}

}

public class AppIdentityDbContext : MultiTenantIdentityDbContext
{
public AppIdentityDbContext (IMultiTenantContextAccessor multiTenantContextAccessor, DbContextOptions 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
Copy link
Author

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

@AndrewTriesToCode
Copy link
Contributor

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Development

No branches or pull requests

2 participants