1

I started an application in Blazor .net 3.1, and I'm having a problem. I will want to add a user with an admin role (root) when starting the application. I am using EF. Adding the user works, but adding roles throws me an exception.

Image exception

System.AggregateException : 'No service for type 'Microsoft.AspNetCore.Identity.RoleManager'1[Microsoft.AspNEtCore.Identity.IdentityRole]' has been registered.ontainer is destroyed)'

I have tried different solutions, like ASP.NET Core Identity Add custom user roles on application startup, old post but I still have the same exception, on SQLite, SQL Server,...

I created a static class and in the Startup.cs I call this method.

public static class RolesData
{
    private static readonly string[] Roles = new string[] { "Admin", "Manager", "Member" };

    public static async Task SeedRoles(IServiceProvider serviceProvider)
    {
        using (var serviceScope = serviceProvider.GetRequiredService<IServiceScopeFactory>().CreateScope())
        {
            var roleManager = serviceScope.ServiceProvider.GetRequiredService<RoleManager<IdentityRole>>();
            foreach (var role in Roles)
            {
                if (!await roleManager.RoleExistsAsync(role))
                {
                    await roleManager.CreateAsync(new IdentityRole(role));
                }
            }
        }
    }
}

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    ...
    RolesData.SeedRoles(app.ApplicationServices).Wait();
}

If you have any suggestions I'm interested, and also if you know of a site that explains authentication with Identity, I want to understand!

Thank you for your help

thatguy
  • 21,059
  • 6
  • 30
  • 40
Ryck
  • 87
  • 1
  • 12

3 Answers3

1

By the error it appears you have not configured the Identity server to expose Roles.

For example in Startup.cs

services.AddDefaultIdentity<ApplicationUser>(options => options.SignIn.RequireConfirmedAccount = true)
                .AddRoles<IdentityRole>()  // <------
                .AddEntityFrameworkStores<ApplicationDbContext>();

I have a standard template with roles seeded here

It goes further to show how to enable the use of the Authorize attribute:

@attribute [Authorize(Roles = "Administrator")]

and AuthorizeView :

<AuthorizeView Roles="Administrator">
    Only Administrators can see this.<br />
</AuthorizeView>
<AuthorizeView Roles="Moderator">
    Only Moderators can see this.<br />
</AuthorizeView>
<AuthorizeView Roles="Moderator,Administrator">
    Administrators and Moderators can see this.
</AuthorizeView>

The changes I made to a standard project to enable Roles and make them visible to Blazor WebAssembly can be seen in this commit

Brian Parker
  • 11,946
  • 2
  • 31
  • 41
  • Thanks ! I like to feel that little pinch in the heart when it works ;) – Ryck Aug 17 '20 at 07:51
  • Broken link bro ! "commit" – JakeL Dec 08 '20 at 04:31
  • @LukeVincent Fixed thanks. Here is a newer repo .net 5.0 https://github.com/BrianLParker/AuthApp/commit/c4cc6e38b738718ebba9fee1882ba3db887b53bf – Brian Parker Dec 08 '20 at 06:21
  • Thank you ever so much Brian!! I have been Googling this IDENTITY to death with zero success until you came along! Now my project can proceed at full steam! P.S. Added bonus, my web app is in net 5.0, so a win win all round. – JakeL Dec 09 '20 at 00:01
  • @BrianParker Hi bro i setup like your example but in my apicontroller connot use ``` [Authorize(Roles ="admin,sysadmin")] ``` Cloud you suggest me? Thanks – รัฐพล.James Jun 15 '23 at 10:17
  • @รัฐพล.James Did you include the bit where i split the role claim into multiple role claims. – Brian Parker Jun 15 '23 at 10:25
  • @BrianParker I am done, I just DI ApiAuthorization follow below answers, Thanks – รัฐพล.James Jun 15 '23 at 10:32
1

Microsoft has a good guide about this:

https://learn.microsoft.com/en-us/aspnet/core/blazor/security/webassembly/hosted-with-identity-server?view=aspnetcore-6.0&tabs=visual-studio#name-and-role-claim-with-api-authorization

In the Client app, create a custom user factory. Identity Server sends multiple roles as a JSON array in a single role claim. A single role is sent as a string value in the claim. The factory creates an individual role claim for each of the user's roles.

CustomUserFactory.cs:

using System.Linq;
using System.Security.Claims;
using System.Text.Json;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication.Internal;

public class CustomUserFactory
    : AccountClaimsPrincipalFactory<RemoteUserAccount>
{
    public CustomUserFactory(IAccessTokenProviderAccessor accessor)
        : base(accessor)
    {
    }

    public override async ValueTask<ClaimsPrincipal> CreateUserAsync(
        RemoteUserAccount account,
        RemoteAuthenticationUserOptions options)
    {
        var user = await base.CreateUserAsync(account, options);

        if (user.Identity.IsAuthenticated)
        {
            var identity = (ClaimsIdentity)user.Identity;
            var roleClaims = identity.FindAll(identity.RoleClaimType).ToArray();

            if (roleClaims.Any())
            {
                foreach (var existingClaim in roleClaims)
                {
                    identity.RemoveClaim(existingClaim);
                }

                var rolesElem = account.AdditionalProperties[identity.RoleClaimType];

                if (rolesElem is JsonElement roles)
                {
                    if (roles.ValueKind == JsonValueKind.Array)
                    {
                        foreach (var role in roles.EnumerateArray())
                        {
                            identity.AddClaim(new Claim(options.RoleClaim, role.GetString()));
                        }
                    }
                    else
                    {
                        identity.AddClaim(new Claim(options.RoleClaim, roles.GetString()));
                    }
                }
            }
        }

        return user;
    }
}

In the Client app, register the factory in Program.cs:

builder.Services.AddApiAuthorization()
    .AddAccountClaimsPrincipalFactory<CustomUserFactory>();

In the Server app, call AddRoles on the Identity builder, which adds role-related services:

using Microsoft.AspNetCore.Identity;

...

services.AddDefaultIdentity<ApplicationUser>(options => 
    options.SignIn.RequireConfirmedAccount = true)
    .AddRoles<IdentityRole>()
    .AddEntityFrameworkStores<ApplicationDbContext>();

API authorization options In the Server app:

  • Configure Identity Server to put the name and role claims into the ID token and access token.
  • Prevent the default mapping for roles in the JWT token handler.

Startup.cs (Program.cs in .NET6):

using System.IdentityModel.Tokens.Jwt;
using System.Linq;

...

services.AddIdentityServer()
    .AddApiAuthorization<ApplicationUser, ApplicationDbContext>(options => {
        options.IdentityResources["openid"].UserClaims.Add("name");
        options.ApiResources.Single().UserClaims.Add("name");
        options.IdentityResources["openid"].UserClaims.Add("role");
        options.ApiResources.Single().UserClaims.Add("role");
    });

JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Remove("role");

Microsofts guide says Use one of the following approaches: API authorization options or Profile Service but using a Profile Service like public class ProfileService : IProfileServic only works with Authorization Code Grant and not Resource Owner Password Credentials.

See here for more info:

https://stackoverflow.com/a/74058054/3850405

In Program.cs for ASP.NET Core 6.0 or later:

using System.Security.Claims;

...

builder.Services.Configure<IdentityOptions>(options => 
    options.ClaimsIdentity.UserIdClaimType = ClaimTypes.NameIdentifier);

In Startup.ConfigureServices for versions of ASP.NET Core earlier than 6.0:

using System.Security.Claims;

...

services.Configure<IdentityOptions>(options => 
    options.ClaimsIdentity.UserIdClaimType = ClaimTypes.NameIdentifier);
Ogglas
  • 62,132
  • 37
  • 328
  • 418
0

See my answer on seeding all kinds of data in ASP.NET Core (works in 3.1) using the IEntityTypeConfiguration

I have not tried it on blazor yet but i think it might worth the try.

Note: Changes requires a db migration.

HMZ
  • 2,949
  • 1
  • 19
  • 30