Username, Email & Password Policy in ASP.NET Core Identity

Username, Email & Password Policy in ASP.NET Core Identity

In this tutorial we will look into Username, Email & Password Policy in ASP.NET Core Identity. First I will explain what Identity offers by default and how can be customize it. Later I will also explain how to create Custom Username, Email & Password Policy in ASP.NET Core Identity.

Password Policy in ASP.NET Core Identity

You can enforce Password Policy in Identity so that the User passwords are made stronger. Some examples are:

  • Passwords should contain one non-alphanumeric character.
  • Passwords should be at least 8 characters long.
  • Passwords should contain at least one uppercase letter (A-Z).

You can configure the password Policy in the ConfigureServices() method of the Startup class, like shown below:

public void ConfigureServices(IServiceCollection services)
{
    services.AddDbContext<AppIdentityDbContext>(options => options.UseSqlServer(Configuration["ConnectionStrings:DefaultConnection"]));
    services.AddIdentity<AppUser, IdentityRole>().AddEntityFrameworkStores<AppIdentityDbContext>().AddDefaultTokenProviders();

    services.Configure<IdentityOptions>(opts => {
        opts.Password.RequiredLength = 8;
        opts.Password.RequireNonAlphanumeric = true;
        opts.Password.RequireLowercase = false;
        opts.Password.RequireUppercase = true;
        opts.Password.RequireDigit = true;
    });

    services.AddControllersWithViews();
}

The services.Configure() method accepts IdentityOptions class, whose properties are set to enforce the password policy.

Before you continue this tutorial I ask you to read my previous tutorial on How to Create, Read, Update & Delete users in ASP.NET Core Identity. This is because here I continue with it the same codes.

The below table provides some of the Properties of the IdentityOptions class that I used:

Name Description
RequiredLength Use this property to provide the minimum length of the password.
RequireNonAlphanumeric When you set this property to true then Identity will require at least one character in your password that is not a digit or a alphabet.
RequireLowercase When you set this property to true then Identity will require at least one lowercase character in the password.
RequireUppercase When you set this property to true then Identity will require at least one Uppercase character in the password.
RequireDigit When you set this property to true then Identity will require at least one digit in the password.

You can now test it by going to the URL – https://localhost:44395/Admin/Create and try creating a new user account by giving the name as admin, email as [email protected], and a weak password as secret.

You will fail to create the account and will see some errors due to a weak password. See the below image:

Password Policy Identity

Now change the password to [email protected]. Since it satisfies the password criteria so this time you will be able to create the new account.

Creating Custom Password Policy

You can also build your own Custom Password Policy for Identity. You can do it by inheriting the PasswordValidator<T> class of Microsoft.AspNetCore.Identity namespace.

Let us create a Custom Password Policy, so create a new folder called IdentityPolicy on the root of the project. Inside this folder create a class called CustomPasswordPolicy.cs. Add the following code to this class.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Identity.Models;
using Microsoft.AspNetCore.Identity;

namespace Identity.IdentityPolicy
{
    public class CustomPasswordPolicy : PasswordValidator<AppUser>
    {
        public override async Task<IdentityResult> ValidateAsync(UserManager<AppUser> manager, AppUser user, string password)
        {
            IdentityResult result = await base.ValidateAsync(manager, user, password);
            List<IdentityError> errors = result.Succeeded ? new List<IdentityError>() : result.Errors.ToList();

            if (password.ToLower().Contains(user.UserName.ToLower()))
            {
                errors.Add(new IdentityError
                {
                    Description = "Password cannot contain username"
                });
            }
            if (password.Contains("123"))
            {
                errors.Add(new IdentityError
                {
                    Description = "Password cannot contain 123 numeric sequence"
                });
            }
            return errors.Count == 0 ? IdentityResult.Success : IdentityResult.Failed(errors.ToArray());
        }
    }
}

I have inherited my class from the PasswordValidator<T> class which defines ValidateAsync() method. I have override the ValidateAsync() method in my class, and inside this method I am implementing my custom password policy.

Create multi-language website in ASP.NET Core – Globalization and Localization with Resource Files in ASP.NET Core

The first code line – IdentityResult result = await base.ValidateAsync(manager, user, password) validates the password according to the password rules given in the ConfigureServices() method of Startup class. It then returns an IdentityResult object.

The IdentityResult object has a Succeeded property through which I can check if the password validates successfully or not, and accordingly the errors, if any, are added to the List object. The below line does this work:

List<IdentityError> errors = result.Succeeded ? new List<IdentityError>() : result.Errors.ToList();

Next I am checking if the password contains the username or the numeric sequence of 123. In that case I am adding the errors to my List object:

if (password.ToLower().Contains(user.UserName.ToLower()))
{
    errors.Add(new IdentityError
    {
        Description = "Password cannot contain username"
    });
}
 
if (password.Contains("123"))
{
    errors.Add(new IdentityError
    {
        Description = "Password cannot contain 123 numeric sequence"
    });
}

Finally, in the last line of code – return errors.Count == 0 ? IdentityResult.Success : IdentityResult.Failed(errors.ToArray()), I am checking the error’s count. If the count is 0 then I return IdentityResult.Success else IdentityResult.Failed is returned with the errors in an array object.

Would you like to dig deep into the world of database programming with C#? Then you can check this series of tutorials on Entity Framework Core that help you to learn database programming within a few hours time.

The password validation functionality is defined by the IPasswordValidator interface of the Microsoft.AspNetCore.Identity namespace. So I need to register my CustomPasswordPolicy class as the password validator for AppUser objects. This is done inside the ConfigureServices() method of the Startup class, as shown in the below code:

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Identity.Models;
using Microsoft.Extensions.Configuration;
using Microsoft.EntityFrameworkCore;
using Microsoft.AspNetCore.Identity;
using Identity.IdentityPolicy;

namespace Identity
{
    public class Startup
    {
        public Startup(IConfiguration configuration) => Configuration = configuration;
        public IConfiguration Configuration { get; }

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddTransient<IPasswordValidator<AppUser>, CustomPasswordPolicy>();
            services.AddDbContext<AppIdentityDbContext>(options => options.UseSqlServer(Configuration["ConnectionStrings:DefaultConnection"]));
            services.AddIdentity<AppUser, IdentityRole>().AddEntityFrameworkStores<AppIdentityDbContext>().AddDefaultTokenProviders();

            services.Configure<IdentityOptions>(opts => {
                opts.Password.RequiredLength = 8;
                opts.Password.RequireNonAlphanumeric = true;
                opts.Password.RequireLowercase = false;
                opts.Password.RequireUppercase = true;
                opts.Password.RequireDigit = true;
            });

            services.AddControllersWithViews();
        }

        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            //...
        }
    }
}

Now it’s time to check my created functionally. Run the application and go to the URL – https://localhost:44395/Admin/Create. Here try adding a new user by providing the following values:

1. Name – Ronny
2. Email – [email protected]
3. Password – ronny123

On clicking the Create button you will see the 2 errors (at the last):

  • 1. Password cannot contain username
  • 2. Password cannot contain 123 numeric sequence

These errors come from the CustomPasswordPolicy class which validates the password based on the policy I implemented.

See the below image which shows these password errors:

Custom Password Policy Identity

Username and Email Policy in Identity

Just like what I did for Password Policy, I can also configure the Startup class for Username and Email Policies. These are done using the IdentityOptions class properties.

Update the ConfigureServices() method of the Startup.cs class to add the below 2 code lines inside the services.Configure<IdentityOptions>() method as highlighted below.

public void ConfigureServices(IServiceCollection services)
{
    services.AddTransient<IPasswordValidator<AppUser>, CustomPasswordPolicy>();
    services.AddDbContext<AppIdentityDbContext>(options => options.UseSqlServer(Configuration["ConnectionStrings:DefaultConnection"]));
    services.AddIdentity<AppUser, IdentityRole>().AddEntityFrameworkStores<AppIdentityDbContext>().AddDefaultTokenProviders();

    services.Configure<IdentityOptions>(opts => {
        opts.User.RequireUniqueEmail = true;
        opts.User.AllowedUserNameCharacters = "abcdefghijklmnopqrstuvwxyz";
        opts.Password.RequiredLength = 8;
        opts.Password.RequireNonAlphanumeric = true;
        opts.Password.RequireLowercase = false;
        opts.Password.RequireUppercase = true;
        opts.Password.RequireDigit = true;
    });

    services.AddControllersWithViews();
}

By setting the RequireUniqueEmail property to true, I enforce that no 2 users in my Identity database can have the same email address.

The next property which I used is the AllowedUserNameCharacters. I have set a list of characters that can only be used when creating a username in Identity.

I already have a user having email [email protected] in my Identity Database. Now when I try adding a new user having email as [email protected] I get errors. Replicate these errors by going to the URL – https://localhost:44395/Admin/Create and try creating a new user having the following values:

1. Name – admin1
2. Email – [email protected]
3. Password – [email protected]#

You will receive 2 errors which are:

  • 1. User name ‘admin1’ is invalid, can only contain letters or digits.
  • 2. Email ‘[email protected]’ is already taken.

These errors are shown in the image given below:

username email policy identity

Creating Custom Username and Email Policy

To create a Custom Username and Email Policy you will need to use the UserValidator class. This class provides the validation service for usernames and emails.

Let us place 2 restrictions:

  • Prevent adding username called Google.
  • Allow only yahoo.com email addresses.

So create a new class called CustomUsernameEmailPolicy inside the Identity folder. Inherit it from UserValidator class and override the ValidateAsync() method to create your own custom username and email policy.

The full code of the CustomUsernameEmailPolicy class is given below:

using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Identity.Models;
using Microsoft.AspNetCore.Identity;

namespace Identity.IdentityPolicy
{
    public class CustomUsernameEmailPolicy : UserValidator<AppUser>
    {
        public override async Task<IdentityResult> ValidateAsync(UserManager<AppUser> manager, AppUser user)
        {
            IdentityResult result = await base.ValidateAsync(manager, user);
            List<IdentityError> errors = result.Succeeded ? new List<IdentityError>() : result.Errors.ToList();

            if (user.UserName == "google")
            {
                errors.Add(new IdentityError
                {
                    Description = "Google cannot be used as a user name"
                });
            }

            if (!user.Email.ToLower().EndsWith("@yahoo.com"))
            {
                errors.Add(new IdentityError
                {
                    Description = "Only yahoo.com email addresses are allowed"
                });
            }
            return errors.Count == 0 ? IdentityResult.Success : IdentityResult.Failed(errors.ToArray());
        }
    }
}

Next, register the CustomUsernameEmailPolicy class in the ConfigureServices() method of the Startup class as highlighted in the below code:

public void ConfigureServices(IServiceCollection services)
{
    services.AddTransient<IPasswordValidator<AppUser>, CustomPasswordPolicy>();
    services.AddTransient<IUserValidator<AppUser>, CustomUsernameEmailPolicy>();
    services.AddDbContext<AppIdentityDbContext>(options => options.UseSqlServer(Configuration["ConnectionStrings:DefaultConnection"]));
    services.AddIdentity<AppUser, IdentityRole>().AddEntityFrameworkStores<AppIdentityDbContext>().AddDefaultTokenProviders();

    services.Configure<IdentityOptions>(opts => {
        opts.User.RequireUniqueEmail = true;
        opts.User.AllowedUserNameCharacters = "abcdefghijklmnopqrstuvwxyz";
        opts.Password.RequiredLength = 8;
        opts.Password.RequireNonAlphanumeric = true;
        opts.Password.RequireLowercase = false;
        opts.Password.RequireUppercase = true;
        opts.Password.RequireDigit = true;
    });

    services.AddControllersWithViews();
}

Now it’s time to check this feature. Go to the https://localhost:44395/Admin/Create url and try adding a new user by providing the following values:

1. Name – tom
2. Email – [email protected]
3. Password – [email protected]

You will receive errors stating that Only yahoo.com email addresses are allowed, as shown in the below image:

username email custom policy identity
In the same way try adding a username called google by entering the following information:

1. Name – google
2. Email – [email protected]
3. Password – [email protected]

You will get error stating – Google cannot be used as a user name. See the below image which shows this error.
Custom Username in Identity

These errors come because my custom username & email validation play a part in these cases.

Applying Password, Username and Email Policies when Updating a User Account

You will notice that your Password, Username and Email Policies, which you added in the above sections, will not be automatically applied when updating details of an existing user.

This you can confirm by running your application and going to the URL – https://localhost:44395/Admin where you will see all the users records. Here click the Update link against any of the User’s record. When the user’s record opens for updating in another page, put 123 for the password and click the save button. You will notice that the user account gets updated and you did not get the error saying – Password cannot contain 123 numeric sequence.

So in order to apply the Custom Password, Username and Email Policies when Updating a User Account, you have to update the Update Action method as given below:

[HttpPost]
public async Task<IActionResult> Update(string id, string email, string password)
{
    AppUser user = await userManager.FindByIdAsync(id);
    if (user != null)
    {
        IdentityResult validEmail = null;
        if (!string.IsNullOrEmpty(email))
        {
            validEmail = await userValidator.ValidateAsync(userManager, user);
            if (validEmail.Succeeded)
                user.Email = email;
            else
                Errors(validEmail);
        }
        else
            ModelState.AddModelError("", "Email cannot be empty");
 
        IdentityResult validPass = null;
        if (!string.IsNullOrEmpty(password))
        {
            validPass = await passwordValidator.ValidateAsync(userManager, user, password);
            if (validPass.Succeeded)
                user.PasswordHash = passwordHasher.HashPassword(user, password);
            else
                Errors(validPass);
        }
        else
            ModelState.AddModelError("", "Password cannot be empty");
 
        if (validEmail != null && validPass != null && validEmail.Succeeded && validPass.Succeeded)
        {
            IdentityResult result = await userManager.UpdateAsync(user);
            if (result.Succeeded)
                return RedirectToAction("Index");
            else
                Errors(result);
        }
    }
    else
        ModelState.AddModelError("", "User Not Found");
 
    return View(user);
}

Also add the dependencies of IPasswordValidator and IUserValidator class in the constructor of the Admin Controller.

public class AdminController : Controller
{
    private UserManager<AppUser> userManager;
    private IPasswordHasher<AppUser> passwordHasher;
    private IPasswordValidator<AppUser> passwordValidator;
    private IUserValidator<AppUser> userValidator;
   
    public AdminController(UserManager<AppUser> usrMgr, IPasswordHasher<AppUser> passwordHash, IPasswordValidator<AppUser> passwordVal, IUserValidator<AppUser> userValid)
    {
        userManager = usrMgr;
        passwordHasher = passwordHash;
        passwordValidator = passwordVal;
        userValidator = userValid;
    }
    
    // removed for brevity
}

Notice that to use my Custom Validation policies for Email and Password I am using the IPasswordValidator and IUserValidator objects as in the code below:

validEmail = await userValidator.ValidateAsync(userManager, user); 
// validates the email according to the email policies 
// (both given in 'ConfigureServices' & 'CustomUsernameEmailPolicy' class)
 
validPass = await passwordValidator.ValidateAsync(userManager, user, password); 
// validates the password according to the password policies 
// (both given in 'ConfigureServices' & 'CustomPasswordPolicy' class)

The below image shows the error (Password cannot contain 123 numeric sequence) when updating a user details and providing 123 in the password field.

password email policy in update user identity

You can download the full codes of this tutorial from the below link:

Download

Conclusion

This completes this tutorial and now you are in a position to create your own custom policies in Identity.

Share this article -

yogihosting

ABOUT THE AUTHOR

This article has been written by the Technical Staff of YogiHosting. Check out other articles on "ASP.NET Core, jQuery, EF Core, SEO, jQuery, HTML" and more.