Role-based Authentication with hosted Blazor template
Tags: blazor, authentication, csharp, dotnet
Published: 2021-01-01 12:48:31

In this small tutorial, I'm going to show you something that I've struggled a lot in the last couple of days: Role-based authentication with the hosted Blazor template from the .NET CLI/Visual Studio.

First of all, we're going to create a new project:
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/iz0n3luxqtgpcwun2dca.png)

If you prefer the CLI:

```bash
dotnet new blazorwasm --auth Individual --hosted
```

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/mb8os5h2qwpl00okyflj.png)

Next, we'll run `dotnet ef database update` in the console inside the Server projects folder so EntityFramework can run the database migrations.

```bash
dotnet ef database update
```

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/f74kfhs3suxaug2ekjy9.png)

Then we need to make some changes in the `Startup.cs` class in the Server project:

You'll find a line inside the `ConfigureServices` method which adds the default Identity to our server:

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

We need to add one line to add our roles to the Identity:

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

Now we need to make some changes to the following code:

```csharp
services.AddIdentityServer()
    .AddApiAuthorization<ApplicationUser, ApplicationDbContext>();
```

We'll expand it, so our Server always adds the role to the JWT token when the user requests his/her token:

```csharp
services.AddIdentityServer()
    .AddApiAuthorization<ApplicationUser, ApplicationDbContext>(options =>
        {
            options.IdentityResources["openid"].UserClaims.Add("role");
            options.ApiResources.Single().UserClaims.Add("role");
        }
    );
System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler
      .DefaultInboundClaimTypeMap.Remove("role");
```

I've also created a small helper method to create an admin Account, create all Roles from an Enum and assigned the Administrator role to the admin Account.

Create a new enum inside the Shared project:

```csharp
// Role.cs
using System.ComponentModel;

namespace Shared
{
    public enum Role
    {
        [Description("Administrator")]
        Administrator,
        [Description("Free")]
        Free,
        [Description("Paid")]
        Paid
    }
}
```

Helper method inside `Startup.cs` of the Server project:

```csharp
private void CreateRoles(IServiceProvider serviceProvider)
{
    var roleManager = serviceProvider.GetRequiredService<RoleManager<IdentityRole>>();
    var userManager = serviceProvider.GetRequiredService<UserManager<ApplicationUser>>();
    Task<IdentityResult> roleResult;
    string email = "admin@domain.com";
    string securePassword = "Pa$$w0rd!";

    foreach (var role in Enum.GetValues(typeof(Role)))
    {
        Task<bool> roleExists = roleManager.RoleExistsAsync(role.ToString());
        roleExists.Wait();

        if (!roleExists.Result)
        {
            roleResult = roleManager.CreateAsync(new IdentityRole(role.ToString()));
            roleResult.Wait();
        }
    }

    Task<ApplicationUser> adminUser = userManager.FindByEmailAsync(email);
    adminUser.Wait();

    if (adminUser.Result == null)
    {
        var admin = new ApplicationUser();
        admin.Email = email;
        admin.UserName = email;

        Task<IdentityResult> newUser = userManager.CreateAsync(admin, securePassword);
        newUser.Wait();
    }

    var createdAdminUser = userManager.FindByEmailAsync(email);
		createdAdminUser.Wait();
    createdAdminUser.Result.EmailConfirmed = true; // confirm email so we can login
    Task<IdentityResult> newUserRoleAssignment = userManager.AddToRoleAsync(createdAdminUser.Result, Role.Administrator.ToString());
    newUserRoleAssignment.Wait();
}
```

Now we need to call this helper method from the `Configure` method inside the `Startup.cs`. But first, we need to change our method signature (we need `IServiceProvider` inside this class, so we'll expect it from .NET Core's built in Dependency Injection:

```csharp
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IServiceProvider serviceProvider)
```

Now we can add the following line at the bottom of the Configure method:

```csharp
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IServiceProvider serviceProvider)
{
	...
	CreateRoles(serviceProvider);
}
```

This change will create our 3 defined Roles inside the database: Administrator, Free and Paid.

After running the app with `dotnet run` you can see, that all 3 roles and our admin user have been created:

(I'm using VSCode with the SQLite extension to view data from the SQLite Database file)

Roles:
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/o9yvb3o71kwpjdwenbzg.png)

Admin User:
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/0dgb6hdk0ywjgpb9hq8q.png)

And the "Administrator" role assignment:
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/faa8hg2djc8oqkeqcwv6.png)

Now, our server knows about Roles and will also send the roles back via the JWT token.

To read and show all Claims from our Blazor frontend, we can expand the `Index.razor` inside our Client project with the following (look for the <AuthorizeView> tag):

```csharp
@page "/"

<h1>Hello, world!</h1>

Welcome to your new app.

<SurveyPrompt Title="How is Blazor working for you?" />

<AuthorizeView>
    <ul>
        @foreach (var item in context.User.Claims)
        {
            <li>Type: @item.Type, Value: @item.Value</li>
        }
    </ul>
</AuthorizeView>
```

This will show us all the claims (and roles) after we've signed in. Let's run the app with `dotnet run` or `dotnet watch run` from inside the Server folder.

We're able to login with our Admin User and as you can see from the image below, our Role is exposed through the token to our Blazor frontend.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/9vqbuor8w466t3ljtvfj.png)

To test this out, we'll make 2 changes to the following files:

NavMenu.razor: wrap the last menu point "Fetch data" with an AuthorizeView component (built into Blazor) and a defined role:

```csharp
// NavMenu.razor
...
<AuthorizeView Roles="Administrator">
    <li class="nav-item px-3">
        <NavLink
            class="nav-link"
            href="fetchdata"
        >
            <span
                class="oi oi-list-rich"
                aria-hidden="true"
            ></span> Fetch data
        </NavLink>
    </li>
</AuthorizeView>
...
```

That means, that any user who has not the Role "Administrator" defined and exposed via the token, won't be able to see this menu element:
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/ylvlidshynam2p0ifxi6.png)

After logging in with the admin user, we can see the menu element again:
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/v43c589t0hjhnv7h4f82.png)

```csharp
namespace BlazorAuth.Server.Controllers
{
    [Authorize] // replace this line
    [Authorize(Roles = "Administrator")] // with this line
    [ApiController]
    [Route("[controller]")]
    public class WeatherForecastController : ControllerBase
    {
       ...
    }
}
```

With this last change, we've secured our Fetch data page on the frontend only for users in the Administrator role and our Controller too. That means, when a user does not have this role, any call to this endpoint (Controller) or the page via the browser, won't get any data back.

I hope that this tutorial helped you. If you have any further questions or feedback to this, ping me on Twitter [https://twitter.com/mecoupz](https://twitter.com/mecoupz) .