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

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

You can enforce Password Policy in Identity System so that the User passwords are made much more 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 Startup class ConfigureServices method, like shown below:

public void ConfigureServices(IServiceCollection services)
{
    services.AddDbContext<AppIdentityDbContext>(options => options.UseSqlServer(Configuration["ConnectionStrings:DefaultConnection"]));
    services.AddIdentity<AppUser, IdentityRole>(opts =>
    {
        opts.Password.RequiredLength = 8;
        opts.Password.RequireNonAlphanumeric = true;
        opts.Password.RequireLowercase = false;
        opts.Password.RequireUppercase = true;
        opts.Password.RequireDigit = true;
    }).AddEntityFrameworkStores<AppIdentityDbContext>().AddDefaultTokenProviders();
    services.AddMvc();
}

The services.AddIdentity() method accepts IdentityOptions object, whose ‘Password’ property that returns PasswordOptions object, can be used 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 Identity Membership System in ASP.NET Core. This is because here I update the same codes to create my policies.

The below table provides some of the Properties of the PasswordOptions class and there use:

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 – ‘/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 the Identity System. You can do it by inheriting the PasswordValidator class.

Let us create a Custom Password Policy, so create a new folder called ‘IdentityPolicy’ in the root of the project. Inside this folder create a classs called ‘CustomPasswordPolicy’ with the code shown below:

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 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.

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 Statup class (see the above section). It then gives me an IdentityResult object.

The IdentityResult object has a ‘Succeeded’ property through which I can check if the password validated 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();

In the next lines of codes 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 count of the errors, if the count is 0 then I return ‘IdentityResult.Success’ else ‘IdentityResult.Failed’ 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 in 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 Identity.Models;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Identity.IdentityPolicy;

namespace Identity
{
    public class Startup
    {
        ...

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddTransient<IPasswordValidator<AppUser>, CustomPasswordPolicy>();
            services.AddDbContext<AppIdentityDbContext>(options => options.UseSqlServer(Configuration["ConnectionStrings:DefaultConnection"]));
            services.AddIdentity<AppUser, IdentityRole>(opts =>
            {
                opts.Password.RequiredLength = 8;
                opts.Password.RequireNonAlphanumeric = true;
                opts.Password.RequireLowercase = false;
                opts.Password.RequireUppercase = true;
                opts.Password.RequireDigit = true;
            }).AddEntityFrameworkStores<AppIdentityDbContext>().AddDefaultTokenProviders();
            services.AddMvc();
        }

        ...
    }
}

Now it’s time to check my created functionally. 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’ 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.User property, which returns an instance of the UserOptions class.

Update the ConfigureServices method to add the below 2 lines inside the services.AddIdentity method:

opts.User.RequireUniqueEmail = true;
opts.User.AllowedUserNameCharacters = "abcdefghijklmnopqrstuvwxyz";

So after adding these 2 lines the updated ConfigureServices method code becomes:

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

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 try adding a new user by providing the following values:

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

I receive 2 errors:

  • 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 which provides the validation service for usernames and emails.

So create a new class called ‘CustomUsernameEmailPolicy’ and inherit it from UserValidator class. Override the ValidateAsync method to check if the email entered is of yahoo.com or not.

If the email is not from yahoo.com then I add an error to List object.

The full code of the ‘CustomUsernameEmailPolicy’ class is given below:

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

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.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 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>(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;
     }).AddEntityFrameworkStores<AppIdentityDbContext>().AddDefaultTokenProviders();
     services.AddMvc();
 }

Now it’s time to check this feature. Go to the ‘/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]

I receive the error that states ‘Only yahoo.com email addresses are allowed’, as shown in the below image:

username email custom policy identity

The error comes because my custom email validation comes to play and prevents from adding a gmail.com email address.

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 to when updating a user in Identity.

This you can confirm by running your application and clicking the ‘Update’ link against any of the User record. When the user’s record opens for updating purpose, 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 User Action method as shown 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:

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;
}

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:

IdentityResult validEmail = await userValidator.ValidateAsync(userManager, user);

validPass = await passwordValidator.ValidateAsync(userManager, user, password);

The below image shows the error due to the presence of ‘123’ numeric sequence in the password:

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 Management System.

Share this article -

yogihosting

ABOUT THE AUTHOR

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