GitHub profile
.NET Framework .NET C# VB.NET LINQ ASP.NET Web API REST SignalR Windows Forms WPF WCF RabbitMQ PHP SQL Server MySQL PostgreSQL MariaDB SQLite MongoDB ADO.NET ORM Entity Framework Dapper XML JSON HTML5 CSS3 Bootstrap JavaScript jQuery Angular React TypeScript NPM Blazor UI/UX Responsive Web Design Redis Elasticsearch GraphQL Grafana Agile Scrum Kanban Windows Server IIS PowerShell Active Directory TFS Azure Automation Software Reverse Engineering Performance Optimization Git Jira/Confluence CI/CD TeamCity SOLID KISS DRY YAGNI
Always will be ready notify the world about expectations as easy as possible: job change page

Part 2: Role-based security with Blazor and Identity Server 4

Created: Oct 12, 2020
Author: Marco De Sanctis
Source: source
Views: 376

In a previous article we’ve introduced how authentication works in Blazor WebAssembly and we’ve seen a simple example on how to create a Blazor client, implement the login flow via Identity Server 4 and retrieve an access token to call a protected Web API.


However, every real-world application will sooner or later need to deal with user roles, in order to offer different functionalities to its users depending on what they have access to.

In this article, we are going apply some changes to the BlazorSecurityDemo application we’ve introduced before, adding role-based security both on the API side and in the UI. The full source code is available at this repository in GitHub:

Let’s get started.

Roles in Identity Server 4

A role in Identity Server 4 is just an attribute we assign to a user, which we can optionally return as a claim.

In its default template, Identity Server 4 uses ASP.NET Core Identity as a backend store, which means it already has everything it needs in place to create roles, assign them to users, and retrieve them when needed.

We’re not going in the narrow details on how to implement these functionality and create a user management application, there’s plenty of blog posts out there on the subject, let’s just use a tool such as DB Browser for SQLite to manually add a couple of roles (User and Admin) to the database and assign them to the sample users (Bob and Alice) in our store:

INSERT INTO AspNetRoles (Id, Name, NormalizedName)
VALUES ("User", "User", "User"),
("Admin", "Admin", "Admin")

-- Both exiting users are of role "User"
;INSERT INTO AspNetUserRoles (UserId, RoleId)
SELECT Id, "User" as RoleId
FROM AspNetUsers

-- Bob is also "Admin"
;INSERT INTO AspNetUserRoles (UserId, RoleId)
SELECT Id, "Admin" as RoleId
FROM AspNetUsers
WHERE Username = 'bob'

Believe it or not, Identity Server is already capable of reading these roles when fetching a user profile, without us doing anything. We just can’t see them because they are not exposed in any token just yet, which is going to be our next step.

There are several ways to accomplish this, like creating a custom profile service, using a new scope or modifying one of the default ones. Probably the latter is the most clean and neat solution: more specifically, we are going to customise the Profile scope, which is already used by our client.

If we look at the Config.cs file, it defines a list of IdentityResources a client can request:

public static IEnumerable<IdentityResource> Ids =>
    new IdentityResource[]
        new IdentityResources.OpenId(),
        new IdentityResources.Profile(),
        new IdentityResources.Email()

Each of these objects has two main properties:

  • a name, which typically corresponds to a scope requested by the client
  • a list of claim types, which the client expects to be included in the token when that scope is requested.

What we are going to do is creating a very simple class, which extends IdentityResources.Profile, so we can include the role claim in the standard Profile scope:

public class ProfileWithRoleIdentityResource : IdentityResources.Profile
    public ProfileWithRoleIdentityResource()

Some internals :) how does Identity Server 4 associate the Role claim type to the actual role we’ve stored in the database? Well, every time a user profile is needed, IS4 internally uses a class from the ASP.NET Core Identity library called UserClaimsPrincipalFactory, which transforms a TUser read from the database in a ClaimsPrincipal. This class will also include a Role claim for each role the user belongs to. At that point, IS4 only needs to cherry pick the claims it needs for the token among those already present in the ClaimsPrincipal!

We now need to replace the default Profile resource with the custom one we just built:

public static IEnumerable<IdentityResource> Ids =>
    new IdentityResource[]
        new IdentityResources.OpenId(),
        // let's include the role claim in the profile
        new ProfileWithRoleIdentityResource(),
        new IdentityResources.Email()

This is all we have to do in Identity Server: we now have everything in place in order to return a role claim in the ID Token. Time to head to our Blazor project and make some changes.

Reading the user roles from Blazor

Even without doing absolutely anything, the Blazor client application is already receiving the new claim, as expected. We can easily verify it browsing the Claims page we created during our previous article, and display all the claims of the current user.

However, at the moment this role claim is not going to be used for any authorisation purposes unless we tell the AuthenticationStateProvider that it represents the actual role of the user in program.cs:

    .AddOidcAuthentication(options =>
        builder.Configuration.Bind("oidc", options.ProviderOptions);
        options.UserOptions.RoleClaim = "role";

Now, we can use the role to authorise access to the FetchData page only to Admins, for example, via the Authorize attribute:

@page "/fetchdata"
@inject HttpClient Http
@attribute [Authorize(Roles = "Admin")]

<h1>Weather forecast</h1>

<p>This component demonstrates fetching data from the server.</p>


We can also hide and display portions of the UI based on the role:

<AuthorizeView Roles="Admin">
        <p>Only admins can see this</p>

Before hitting F5 and testing it, let’s just make one last change to the routing in App.razor in order to acknowledge that a user can be authenticated, but still not authorised to visit a specific page:

    <Router AppAssembly="@typeof(Program).Assembly">
        <Found Context="routeData">
            <AuthorizeRouteView ... ">
                    @if (context.User.Identity.IsAuthenticated)
                        <p>Sorry you are not authorized</p>
                        <RedirectToLogin />

Note: this change is needed because otherwise, if not authorised, we would be stuck in a loop which would endlessly send us back to the Login page.

Now, if we try to access the FetchData page when logged in as Alice, who’s just a normal User, we’ll see the non-authorised message as expected:

What’s annoying, though, is that we see the same error even when using Bob, despite him actually being an Admin. Why is this happening?

Claims and arrays

This is unfortunately due to an issue Blazor currently has with claims with multiple values, as it’s the case with Bob, who’s both User and Admin. In this case, the Authorize attribute expects to find 2 distinct claims in the current Principal, one per role the user belongs to.

What Blazor does instead — via a class called AccountClaimsPrincipalFactory — is generating one single claim, whose value is a JSON array, which is not interpreted correctly by the rest of the security infrastructure.

I’ve recently opened an issue about this on GitHub, and as the team recommend, the solution is creating our own custom ClaimsPrincipalFactory, and generate the claims in the correct form.

The full code is in GitHub, however the interesting bit is the following:

public class ArrayClaimsPrincipalFactory<TAccount>

    public override ValueTask<ClaimsPrincipal> CreateUserAsync(...)
        if (value != null &&
            value is JsonElement element &&
            element.ValueKind == JsonValueKind.Array)
            // remove the wrong claim created

            // add an array of claims to the ClaimsIdentity
            var claims = element.EnumerateArray().Select(x => new Claim(kvp.Key, x.ToString()));


Then we must replace the default factory with our custom one in the program.cs file:

    .AddOidcAuthentication(options =>

If we now visualise the user claims again, we can see the role claims in their correct form:

This also means that, since we’re Admins, we can finally navigate to the FetchData page and see the weather forecasts :)

Never trust the client!

All seems to be working fine, however the current solution is definitely not bullet proof: we should never forget the Blazor WebAssembly is a public client, it runs on the user browser, and as such it can be easily tweaked and modified by a malicious user.

We need to implement a similar level of security also on the server side, and restrict access to the Weather API only to Admins:

[Authorize(Roles = "Admin")]
public class WeatherForecastController : ControllerBase

This requires a new setting on Identity Server to include the role claims also in the access token. We can do it from the usual Config.cs class:

public static IEnumerable<ApiResource> Apis =>
    new ApiResource[]
        // the api requires the role claim
        new ApiResource(
            "The Weather API",
            claimTypes: new[] { JwtClaimTypes.Role })

Fortunately, ASP.NET Core can handle arrays of claims out of the box, as opposite to Blazor, and therefore we don’t need any other customisation here.

We can now hit F5 and verify that everything keeps working as expected, although now we finally have also server-side security.


In this article we’ve started from our previous working example of authentication in Blazor, and we’ve added the support to roles.

We’ve seen how exposing roles as claims in both ID Tokens and Access Tokens requires limited changes in Identity Server, and how they are immediately available to be used in Blazor — although with a minor tweak to handle arrays.

We then made sure that also the Web API implements the same role-based authorisation; this is a best practice since Blazor WebAssembly is considered a public client, it runs in the user’s browser and cannot be trusted for security.

In a next article, we’ll introduce a more powerful concept on how authorize access: policies, we’ll see how we can implement and share them between the ASP.Net Core and Blazor WebAssembly.

The full source code is available at this repository in GitHub:

Oct 24, 2022
Author: Chandan Das
For as long as web applications have been around, full-stack developers have had to work with different sets of technologies for the front and backend. For instance, a developer would use something like Angular for the frontend and Express.js for...
Nov 25, 2022
Author: Amit Naik
In this article, you will see a Web API solution template which is built on Hexagonal Architecture with all essential features using .NET Core. Download source code from GitHub Download...
Feb 7, 2021
Author: Sahan Serasinghe
Caching is the process of storing the data that’s frequently used so that data can be served faster for any future requests. Suppose we have a very lightweight process which talks to another server whose data is not going to...
Mar 19, 2021
Author: Mukesh Murugan
In this article, We will talk about Onion Architecture In ASP.NET Core and it’s advantages. We will also together build a WebApi that follows a variant of Onion Architecture so that we get to see why it is important to...
Send message
Your name

© 1999–2023 WebDynamics
1980–... Sergey Drozdov
Area of interests: .NET | .NET Core | C# | ASP.NET | Windows Forms | WPF | Windows Phone | HTML5 | CSS3 | jQuery | AJAX | MS SQL Server | Transact-SQL | ADO.NET | Entity Framework | IIS | OOP | OOA | OOD | WCF | WPF | MSMQ | MVC | MVP | MVVM | Design Patterns | Enterprise Architecture | Scrum | Kanban