Multi-Tenant does not cater for Role with same Role-Name in > 1

I am still encountering the same issue (link 1, link 2), even with the latest version of Radzen Blazor Studio installed (version 1.13.2).

The key issue here is that roles are being looked up by name, and when queried from the database, it returns the first role it finds – the one that was created first.

Steps to Reproduce:

  1. Create the first tenant, "tenant_1."
  2. Create the second tenant, "tenant_2."
  3. Add two roles to the first tenant: "User" and "Admin."
  4. Add the same two roles to the second tenant: "User" and "Admin."
  5. Create a new user for the second(!) tenant, "tenant_2," and attempt to assign a role.

Result:

  • If you inspect the new user in the application, you'll notice that no role is attached.
  • In the database, a record will be added to the "AspNetUserRoles" table for the new user. However, the attached role will be the one with the same name but associated with the first created tenant, "tenant_1," even though we were creating the user for a different tenant, "tenant_2."

Possible Causes:
Upon examining the code execution sequence, it becomes evident that the issue arises during the following steps:
-> AddApplicationUser.FormSubmit(Models.ApplicationUser)
-> SecurityService.CreateUser(Models.ApplicationUser)
-> (invoke Post request to ApplicationUsers with new user data)
-> ApplicationUsersController.Post([FromBody] ApplicationUser)
-> UserManager.AddToRolesAsync(user, roles.Select(r => r.Name))

At this stage, the "AddToRolesAsync" method is executed within the "UserManager" class, which is located in another assembly and cannot be independently modified. The problem with this method is that it assigns roles solely based on their names, without considering the tenant ID. Consequently, it assigns to a new user only those roles that were created for the first tenant, "tenant_1."

This error will occur with any number of tenants greater than 1. It is only possible to correctly modify roles for the initially created tenant.

Proposed Solution:
In my opinion, an excellent solution would be to create global roles that are visible to all tenants. These roles could only be created by one user, "tenantsadmin." Additionally, you could create local roles that are specific to each tenant and are not visible to other tenants.

Using this solution will also make it easier to manage page access based on roles within the Blazor Studio editor. Global roles can be utilized for these purposes. Since roles are currently dynamically created within the application and can even be created after publication, their primary goal of separating page access is lost.

Here is what you can add to the MultiTenancyUserStore in your app to avoid this:

public override async Task AddToRoleAsync(ApplicationUser user, string normalizedRoleName, CancellationToken cancellationToken = default)
{
    if (user.NormalizedUserName.ToLower() == "tenantsadmin")
    {
        await base.AddToRoleAsync(user, normalizedRoleName, cancellationToken);
    }

    var tenant = GetTenant();
    ApplicationRole role = null;

    if (tenant != null)
    {
        role = await Context.Set<ApplicationRole>().SingleOrDefaultAsync(r => r.NormalizedName == normalizedRoleName && r.TenantId == tenant.Id, cancellationToken);
    }

    Context.Set<IdentityUserRole<string>>().Add(new IdentityUserRole<string>
    {
        RoleId = role.Id,
        UserId = user.Id
    });

    await Context.SaveChangesAsync();
}

We will add it to our templates as well.

1 Like