Initial adding admin roles and users to the database for Role-based authorization in the ASP.NET Core 3.1 MVC applications

Spread the love

There is a really good article in the official ASP.NET Core documentation, which explains how to apply role restrictions to your controller actions. But after reading, it still was hard for me to get answers for a couple of questions:

  • How to seed initial records of the users and roles to the database?
  • And how to assign the new user to the default role?

Let’s find out what we can do.

Initial project

For the sake of simplicity, I started with a new Asp.Net Core 3.1 MVC Web app project which has Identity authentication included.

Create a new ASP.NET Core web application wizard

Then I added the new AdminOnly() action to the Home controller and appropriate AdminOnly.cshtml view to the Views/Home directory. The AdminOnly() action is intended to be allowed only to those users, which are included in the RequireAdministratorRole policy.

[Authorize(Policy = "RequireAdministratorRole")]
public IActionResult AdminOnly()
{
    return View();
}

To perform the action from the user interface the new link was added to the main menu in the Shared/Layout.cshtml.

<li class="nav-item">
    <a class="nav-link text-dark" asp-area="" 
        asp-controller="Home" asp-action="AdminOnly">Admins go here</a>
</li>

And the final thing to do was registering a policy as part of the authorization service configuration and adding role service to Identity.

public void ConfigureServices(IServiceCollection services)
{
    services.AddDbContext<ApplicationDbContext>(options =>
        options.UseSqlServer(
            Configuration.GetConnectionString("DefaultConnection")));
    services
        .AddDefaultIdentity<IdentityUser>(options => 
             options.SignIn.RequireConfirmedAccount = true)
        .AddRoles<IdentityRole>()
        .AddEntityFrameworkStores<ApplicationDbContext>();
    services.AddControllersWithViews();
    services.AddRazorPages();

    services.AddAuthorization(options =>
    {
        options.AddPolicy("RequireAdministratorRole",
             policy => policy.RequireRole("Administrator"));
    });
}

At this stage, the application denies access to the AdminOnly page.

Storing admin credentials

It’s not a good idea to hardcode admin credentials. Of course, if you developed an application for personal needs, the hardcoding approach is still ok. But, it is much flexible to store the admin credentials in the appsettings.json file where the entire application should be configured.

So, let’s add the next section to the appsettings.json

"AdminCredentials": {
    "Email": "admin@romansimuta.com",
    "Password": "Admin_2020"
  }

Notes.

If there is a requirement to store admin login and password in a secure way, you can use the secrets approach.

Seeding admin role and user to the database

The best time to add the administrator’s user and the administrator’s role is the first load of the application. It’s because it should be done only once. So, let’s use the Main() method of the Program.cs.

public static async Task Main(string[] args)
        {
            var host = CreateHostBuilder(args).Build();
            using (var scope = host.Services.CreateScope())
            {
               await DbInitializer.Initialize(scope.ServiceProvider);
            }
            host.Run();
        }

Here we are calling asynchronous DbInitializer.Initialize(IServiceProvider serviceProvider) method which requires service provider instance as an argument. This method isn’t declared yet, so, let’s create a new DbInitializer.cs file in the Data folder and paste the next code inside:

using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Threading.Tasks;

namespace RoleBasedAuth.Data
{
    public static class DbInitializer
    {
        public static async Task Initialize(IServiceProvider serviceProvider)
        {
            var context = serviceProvider
                .GetRequiredService<ApplicationDbContext>();
            context.Database.EnsureCreated();

            var roleManager = serviceProvider
                .GetRequiredService<RoleManager<IdentityRole>>();
            var roleName = "Administrator";
            IdentityResult result;

            var roleExist = await roleManager.RoleExistsAsync(roleName);
            if (!roleExist)
            {
                result = await roleManager
                    .CreateAsync(new IdentityRole(roleName));
                if (result.Succeeded)
                {
                    var userManager = serviceProvider
                        .GetRequiredService<UserManager<IdentityUser>>();
                    var config = serviceProvider
                        .GetRequiredService<IConfiguration>();
                    var admin = await userManager
                        .FindByEmailAsync(config["AdminCredentials:Email"]);
                    
                    if (admin == null)
                    {
                        admin = new IdentityUser()
                        {
                            UserName = config["AdminCredentials:Email"],
                            Email = config["AdminCredentials:Email"],
                            EmailConfirmed = true
                        };

                        result = await userManager
                            .CreateAsync(admin, config["AdminCredentials:Password"]);
                        if (result.Succeeded)
                        {
                            result = await userManager
                                .AddToRoleAsync(admin, roleName);
                            if (!result.Succeeded)
                            {
                                // todo: process errors
                            }
                        }
                    }
                }
            }
        }
    }
}

And that’s all! Now, on the first application run, our code adds a new “Administrator” role, then it creates a new user and includes it to this role. User credentials are taken from the appsettings.json.

And now the application allows this user to visit the AdminOnly page.

Check and try by yourself

The whole code solution with all the code above can be found on GitHub inside of the RoleBasedAuth repository.

Leave a Reply

Your email address will not be published. Required fields are marked *