0

While there seems to be a lot of documentation about verifying roles, claims, etc with ASP.NET Core, there is little information about initially setting these things up in my app.

theutz
  • 9,997
  • 4
  • 15
  • 19

1 Answers1

11

The best way to configure roles, claims, etc, is in your app startup. The new ASP.NET Core Dependency Injection makes setting this up a breeze if you know what you're doing. Most of your work will happen in the Startup.cs file at the root of your project.

1. Setup User Secrets

Don't share your new user secrets with the world by hard-coding them into repositories that may be shared. Luckily, Microsoft has provided a great tool for this. This article explains it in detail: Safe Storage of App Secrets

To make sure this service is available later on, check the Startup constructor method in Startup.cs:

public Startup(IHostingEnvironment env) {
    ...
    if (env.IsDevelopment()) {
        // BELOW IS THE IMPORTANT LINE
        builder.AddUserSecrets();
    }
    ...
    // This is important, too. It sets up a readonly property
    // that you can use to access your user secrets.
    Configuration = builder.Build();
}

// This is the read-only property
public IConfigurationRoot Configuration { get; }

2. Set Up Your Application Database

I'm using Entity Framework Core for my persistence store. This code was generated automatically when I created my application with the Web App template. But I'll include it here for reference and troubleshooting (still in Startup.cs):

public void ConfigureServices(IServiceCollection services)
{
    // My Db Context is named "ApplicationDbContext", which is the
    // default name. Yours might be something different.
    // Additionally, if you're using a persistence store other than
    // MSSQL Server, you might have a different set of options here.
    services.AddDbContext<ApplicationDbContext>(options =>
        options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));

    // This sets up the basics of the Identity code. "ApplicationUser"
    // is the name of the model that I use for my basic user. It's simply
    // a POCO that can be modified like any EF model, and it's the default
    // name for a user in the template. "ApplicationRole" is a class that I
    // wrote that inherits from the "IdentityRole" base class. I use it to
    // add a role description, and any other future data I might want to
    // include with my role. I then tell the Identity code to store it's
    // data in the "ApplicationDbContext" that I just setup.
    services.AddIdentity<ApplicationUser, ApplicationRole>()
        .AddEntityFrameworkStores<ApplicationDbContext>()
        .AddDefaultTokenProvider();

    // This sets up the MVC framework.
    services.AddMvc();
    ...
}

3. Create hooks in Configure method

This is where the real work starts. You'll want to configure a role with full administrative privileges and assign a first user to that role. I have chosen to put that code in a private method in Startup.cs that I call from within the Configure method. First, the calling code:

// This method is not async out-of-the-box. Add the `async` modifier
// but keep the return type as `void`, since the signature needs to
// stay the same or you'll get a 500 error. We mark it as async because
// the Identity methods are mostly async methods.
public async void Configure(
    IApplicationBuilder app,
    IHostingEnvironment env,
    ILoggerFactory loggerFactory)
{
    ...
    // Default ASP.NET Core route (generated out of the box)
    // I've included this so you know where to put your code!
    app.UseMvc(routes =>
    {
        routes.MapRoute(
            name: "default",
            template: "{controller=Home}/{action=Index}/{id?}");
    });

    // Her, we call the code that setups up our roles and our first user.
    // These are methods added to the `Startup` class. We use the
    // IApplicationBuilder variable to pass in a User and Role
    // Manager instance from the application services.
    await CreateRoles(
        app.ApplicationServices
            .GetRequiredService<RoleManager<ApplicationRole>>());
    await ConfigureSiteAdmin(
        app.ApplicationServices
            .GetRequiredService<RoleManager<ApplicationRole>>(),
        app.ApplicationServices
            .GetRequiredService<UserManager<ApplicationUser>>()
    );
}

I have found it useful to setup a static class that stores my role names. This allows me to check the names at compiletime, and gives me Intellisense help throughout my code when I need to invoke the role name elsewhere. It looks like this:

public static class RoleNames
{
    public const string SiteAdmin = "Site Admin";
    public const string CompanyAdmin = "Company Admin";
    ...
}

4. Set up Your Roles

Having done that, now we get to set up our roles. Remember, I used ApplicationUser as my user type and ApplicationRole as my role type. You may name yours differently. Add these methods to the bottom of the Startup.cs file:

private async Task CreateRoles(RoleManager<ApplicationRole> roleManager)
{
    var roles = new List<ApplicationRole>
    {
        // These are just the roles I made up. You can make your own!
        new ApplicationRole {Name = RoleName.SiteAdmin,
                             Description = "Full access to all features."},
        new ApplicationRole {Name = RoleName.CompanyAdmin,
                             Description = "Full access to features within their company."}
    };

    foreach (var role in roles)
    {
        if (await roleManager.RoleExistsAsync(role.Name)) continue;
        var result = await roleManager.CreateAsync(role);
        if (result.Succeeded) continue;

        // If we get here, something went wrong.
        throw new Exception($"Could not create '{role.Name}' role.");
    }
}

5. Create your new super user

Now we setup the method that's used to create the admin. We check to make sure that the user doesn't exist yet. The user name is stored using the dotnet user secrets mentioned above. We also check to make sure that our primary admin role is created so that we can immediately assign this user to that role.

private async Task ConfigureSiteAdmin(
    RoleManager<ApplicationRole> roleManager,
    UserManager<ApplicationUser> userManager)
{
    if (await userManager.FindByEmailAsync(Configuration["SiteAdminEmail"]) != null)
        return;
    if (!await roleManager.RoleExistsAsync(RoleName.SiteAdmin))
        throw new Exception($"The {RoleName.SiteAdmin} role has not yet been created.");

    var user = new ApplicationUser
    {
        UserName = Configuration["SiteAdminEmail"],
        Email = Configuration["SiteAdminEmail"],
    };

    await userManager.CreateAsync(user, Configuration["SiteAdminPassword"]);
    await userManager.AddToRoleAsync(user, RoleName.SiteAdmin);
}

6. Enjoy!

I hope this helped you. I had a heck of a time finding all this information scattered throughout the web. If you have any suggestions for improvement, please let me know!

theutz
  • 9,997
  • 4
  • 15
  • 19
  • 1
    Welcome to StackOverflow. You should consider adding some of this content to the _Documentation_ portion of StackOverflow. See the tabs at the top of the page. – David Tansey Nov 18 '16 at 22:12
  • 1
    Consider using this pattern (http://stackoverflow.com/documentation/asp.net-core/1949/dependency-injection/17400/using-scoped-services-during-application-startup-database-seeding#t=201611182214295112758) for seeding or working with scoped services within application startup, because at this time no scope exists. – Tseng Nov 18 '16 at 22:17