I'm working on a client-side blazor application with the last webassembly version (3.2.0).
I started the project from the visual tool with enabling local authentications and I tried to add roles.
First, I added the roles in the ApplicationDbContext :
public class ApplicationDbContext : ApiAuthorizationDbContext<ApplicationUser>
{
public ApplicationDbContext(
DbContextOptions options,
IOptions<OperationalStoreOptions> operationalStoreOptions) : base(options, operationalStoreOptions)
{
}
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
builder.Entity<IdentityRole>()
.HasData(new IdentityRole { Name = "User", NormalizedName = "USER", Id = Guid.NewGuid().ToString(), ConcurrencyStamp = Guid.NewGuid().ToString() });
builder.Entity<IdentityRole>()
.HasData(new IdentityRole { Name = "Admin", NormalizedName = "ADMIN", Id = Guid.NewGuid().ToString(), ConcurrencyStamp = Guid.NewGuid().ToString() });
}
}
Then I added Roles to the IdentityBuilder in the startup class :
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection")));
services.AddDefaultIdentity<ApplicationUser>(options => options.SignIn.RequireConfirmedAccount = true)
.AddRoles<IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>();
services.AddIdentityServer()
.AddApiAuthorization<ApplicationUser, ApplicationDbContext>();
services.AddAuthentication()
.AddIdentityServerJwt();
services.AddControllersWithViews();
services.AddRazorPages();
}
And then in my DbInitializer I created an Admin account with both roles :
private async Task SeedASPIdentityCoreAsync()
{
if (!await context.Users.AnyAsync())
{
var admin = new ApplicationUser()
{
UserName = "[email protected]",
Email = "[email protected]",
EmailConfirmed = true,
};
var result = await userManager.CreateAsync(admin, "aA&123");
if (!result.Succeeded)
{
throw new Exception(result.Errors.First().Description);
}
result = await userManager.AddClaimsAsync(admin, new Claim[]
{
new Claim(JwtClaimTypes.Email, "[email protected]"),
new Claim(JwtClaimTypes.Name, "[email protected]")
});
ApplicationUser user = await userManager.FindByNameAsync("[email protected]");
try
{
result = await userManager.AddToRoleAsync(user, "User");
result = await userManager.AddToRoleAsync(user, "Admin");
}
catch
{
await userManager.DeleteAsync(user);
throw;
}
if (!result.Succeeded)
{
await userManager.DeleteAsync(user);
throw new Exception(result.Errors.First().Description);
}
}
}
But the roles doen't appear in the JWT, and the client-side has no idea about the roles.
How can I add the roles in the JWT, as with the new version of blazor, there is no need of the LoginController ? (If i well understood the changes)
Ok I found what I needed :
1) Create a CustomUserFactory in your client App
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 async override 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);
if (roleClaims != null && 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;
}
}
2) Register the client factory
builder.Services.AddApiAuthorization()
.AddAccountClaimsPrincipalFactory<CustomUserFactory>();
3) In the Server App, call IdentityBuilder.AddRoles
services.AddDefaultIdentity<ApplicationUser>(options =>
options.SignIn.RequireConfirmedAccount = true)
.AddRoles<IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>();
4) Configure Identity Server
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");
There is an other way, by creating a ProfileService
5) User Authorization mechanisms :
<AuthorizeView Roles="admin">