loading...
Cover image for ASP.NET Core Identity - ConfigureServices Essentials

ASP.NET Core Identity - ConfigureServices Essentials

andy_preston profile image Andy Preston Originally published at andyp.dev ・6 min read

This article was originally posted from my blog.

I recently ported an old webapp from ASP.NET Core 2.0 to 3.1, using Identity to handle authorisation and authentication. Coming from Core 2.0, there's some changes in 3.1 which really felt like gotchas, and initially didn't made sense to me based on the information in the official documentation.

This guide describes how I configured ASP.NET Core 3.1 Identity, along with some complimentary information you may find useful. Assumptions are made that this information will be applied to an ASP.NET Core 3 MVC/Razor pages project.

Hopefully the contents of this article will teach you the essentials needed to setup your ConfigureServices method.

Startup.cs and ConfigureServices - Configure Identity services

Indentity services are added inside ConfigureServices, which is located inside your project's your startup.cs file.

An Example ConfigureServices method.

The below code snippet is an example of the default ConfigureServices method inside startup.cs. This is assuming you ticked the box to Enable Authorisation and selected the option for Individual User Accounts when you created the project.

      // This method gets called by the runtime. Use this method to add services to the container.
      public void ConfigureServices(IServiceCollection services)
      {
          services.AddDbContext<ApplicationDbContext>(options =>
              options.UseSqlServer(
                  Configuration.GetConnectionString("DefaultConnection")));
          services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
              .AddEntityFrameworkStores<ApplicationDbContext>();
          services.AddControllersWithViews();
          services.AddMvc();
      }

Enable user accounts

Services.addDbContext

Here is where would usually configure the connection string for Identity. 

I've found that the best approach is to setup one DbContext for Identity, and a seperate DbContext for storing other application data. This modularisation can help to scale your app, if you need to grow in the future. The trick is to place all Identity data in a seperate database to your application data.

services.AddDbContext<ApplicationDbContext>(options =>
              options.UseSqlServer(
                  Configuration.GetConnectionString("DefaultConnection")));

AddDefaultIdentity

Adds a set of common identity services to the application, including a default UI, token providers, and configures authentication to use identity cookies.

services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
                .AddEntityFrameworkStores<ApplicationDbContext>();

That covers the default services added to the ConfigureServices when you start a new project with Identity. Now lets look at some additional services you can add.

Add AuthorizeFilter() to MVC service

We use MVC for request handling in ASP.NET Core. Adding AuthorizeFilter() to the MVC Dependancy Injection service will redirect all requests to the login page if the user is not logged in.

services.AddMvc(options =>
{
    options.Filters.Add(new AuthorizeFilter());
});

You can specify additional policies such as the below which restricts access to Admin and SuperUser users. If you don't specify a policy then default values will be used by AuthorizeFilter().

var policy = new AuthorizationPolicyBuilder()
        .RequireAuthenticatedUser()
        .RequireRole("Admin", "SuperUser")
        .Build();

services.AddMvc(options =>
{
    options.Filters.Add(new AuthorizeFilter(policy));
});

Specify your password requirements - services.Configure<IdentityOptions>

At some point you'll likely want to define the level of complexity of user passwords in your ASP.NET Core web app. 

You can specify the minimum length of user's passwords, toggle the requirement for lower and upper case characters, specify which characters are allowed in the password and also require users have a unique email address. 

The beauty is that you don't need to write complex code to implement this yourself since it's all handled with some easy dependancy injection.

services.Configure<IdentityOptions>(options =>
            {
                options.Password.RequiredLength = 8;
                options.Password.RequireUppercase = true;
                options.Password.RequireLowercase = true;

                options.User.AllowedUserNameCharacters =
                        "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._@+";
                options.User.RequireUniqueEmail = true;

            });

ConfigureApplicationCookie

This is a big one. ConfigureApplicationCookie is where we define the handling of authorization cookies, it's also an area where I found little helpful documentation (when Core 3.1 released).

options.Cookie.HttpOnly

The HttpOnly flag instructs the browser that the cookie should only be accessed by the server and not any clientside scripts. This is very important for preventing XSS (Cross-site scripting) attacks. The flag should only be set to false if you know what you're doing!

options.LoginPath

Just as it sounds. This is the path that your users will be redirected to, if they need to login.

options.AccessDeniedPath

The path that users will be redirected to, if they try to access a resource that they are not authorized to view.

options.SlidingExpiration

SlidingExpiration resets the expiry DateTime of the cookie, so a new cookie is issued with a new expiry time, if the current time is more than halfway through the lifespan of the cookie. This means that users will stay logged in and the cookie stays valid, providing that the user visits the site at least once between halfway through the cookie life, and the expiry time.

So if the cookie expires in 12 hours, the cookie will be re-issued if the user visits the site after 6 hours, but before 12 hours after the cookie was issued.

options.ExpireTimeSpan

Which brings us onto the next essential option you should be aware of. ExpireTimeSpan is where you can confgure a custom expiry time for your cookies.

TimeSpan.FromHours(24) sets the expiry date to 24 hours from now. However you can also use .FromDays() or .FromMinutes() to specify a custom expiry length.

options.Cookie.Name

Configure the name of the Identity cookie with options.Cookie.Name.

services.ConfigureApplicationCookie(options =>
            {
                // Cookie settings
                options.Cookie.HttpOnly = true; // define the cookie for http/https requests only
                options.LoginPath = "/Identity/Account/Login"; // Set here login path.
                options.AccessDeniedPath = "/Identity/Account/AccessDenied"; // set here access denied path.
                options.SlidingExpiration = true; // resets cookie expiration if more than half way through lifespan
                options.ExpireTimeSpan = TimeSpan.FromHours(24); // cookie validation time
                options.Cookie.Name = "myExampleCookie"; // name of the cookie saved to user's browsers
            });

AddDataProtection

By default, when you restart an ASP.Net Core webapp, the application will lose track of which cookies are valid. All Identity users will need to login again, even if they visit the site before the cookie has expired.

With services.AddDataProtection, you can instruct your application to save the keys to the hard disk for recovery when the app is restarted. Note that this is suitable if you are deploying your app to a single server, but for multi-server deployments you should use a networked key-store or a database.

PersistKeysToFileSystem

By specifying a directory path in PersistKeysToFileSystem(), you can specify the folder where the keys should be stored.

SetApplicationName

It's possible for the key vault to store keys from different applications in the same folder. Separate keys by setting a unique application name.

SetDefaultKeyLifetime

The default key lifetime specifies the default lifespan of the keys in the key vault.

services.AddDataProtection()
                .PersistKeysToFileSystem(new DirectoryInfo("\\DataProtection\\Keys"))
                .SetApplicationName("myExampleApp")
                .SetDefaultKeyLifetime(TimeSpan.FromDays(90)
    );

A Full Example ConfigureServices Method

I hope some of this information has been useful to you. Here is a full example of the ConfigureServices method that you might use in production.

public void ConfigureServices(IServiceCollection services)
{
    services.AddDbContext<ApplicationDbContext>(options =>
        options.UseSqlServer(
            Configuration.GetConnectionString("DefaultConnection")));

    services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
        .AddEntityFrameworkStores<ApplicationDbContext>();

    services.AddControllersWithViews().AddRazorRuntimeCompilation();
    services.AddMvc(options =>
    {
        options.Filters.Add(new AuthorizeFilter());
    });

    // Set requirements for security
    services.Configure<IdentityOptions>(options =>
    {
        options.Password.RequiredLength = 8;
        options.Password.RequireUppercase = true;
        options.Password.RequireLowercase = true;

        // Default User settings.
        options.User.AllowedUserNameCharacters =
                "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._@+";
        options.User.RequireUniqueEmail = true;

    });

    services.ConfigureApplicationCookie(options =>
    {
        // Cookie settings
        options.Cookie.HttpOnly = true;
        options.LoginPath = "/Identity/Account/Login"; // Set here login path.
        options.AccessDeniedPath = "/Identity/Account/AccessDenied"; // set here access denied path.
        options.SlidingExpiration = true; // resets cookie expiration if more than half way through lifespan
        options.ExpireTimeSpan = TimeSpan.from(60); // cookie validation time
        options.Cookie.Name = "myExampleCookieName";
    });

    services.AddDataProtection()
        .PersistKeysToFileSystem(new DirectoryInfo("\\DataProtection\\Keys"))
        .SetApplicationName("myExampleApp")
        .SetDefaultKeyLifetime(TimeSpan.FromDays(90));
}

 

Discussion

pic
Editor guide