Username Custom validation in identity ValidateAsync

Earlier I have posted few tutorials about Identity framework, in this post we see how to custom validation in Identity framework works, custom username and password validation method provided by indentify framework.

Identity custom username password validation
Why custom validation?

Nowadays all modern business application has various business logic for creating new user, which are very different in nature from other general application, using indentify framework we can easily apply such type of custom business logic for username and password.

Identity framework provides a different mechanism to validate username and password, which is different type of validation that we often write in model validation.

So, following three things we will learn in this post.

  • How to use standard password validation using Indentify framework in asp.net core.
  • How to write identity custom validation class and register them in startup.cs file.
  • Finally, how to pass identity validation error message to model error message, so we can display error details in user screen.

If you are not familiar with indentify framework, read how to setup indentify framework in asp.net core application.

standard password validation Indentify options

There are some built-in validation options in Indentify framework, which allow us to configure and use some standard validation for username and password, which is very easy to use.

Just open your startup file and add following code in public void ConfigureServices(IServiceCollection services) method.

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

As you can see in above code, we have simply set some rules for username and password just by configuring built-in options.

Send identity validation error message to model

As you can see in above code, the error messages will be generated in identity framework if any of above rules is broken, now the question is how you display those error messages to end user screen!

To do that, we need to transfer those messages to our model, which is consumed in view pages. We retrieve error details from IdentityResult.Errors object.

public async Task<IActionResult> register(UserModel model)
{             
	// removed other codes
	
	IdentityResult _userCreated = await _userManager.CreateAsync(user, model.Password);
	// add identity errors to model errors
	foreach (var err in _userCreated.Errors)
	{
		ModelState.AddModelError(err.Code, err.Description);
	}
	return  View(model);
}

Now your standard model error messages will have all the error description.

Custom rules for username and password

Now in today’s business you may come across requirement, when you need to check lot more additional attribute or business logic from database before your system allow user to create any username. In such situation, we have to write a custom class to integrate with identity framework options.

Here is how you can create a UsernameCustomPolicy : UserValidator<AppUser> class to define all rules you want to have!

    public interface ICustomUserValidator<TUser> : IUserValidator<TUser> where TUser : AppUser
    {
    }

    public class UsernameCustomPolicy<TUser> : UserValidator<AppUser>, ICustomUserValidator<AppUser>
    {
        
        public override async Task<IdentityResult> ValidateAsync(UserManager<AppUser> manager,
            AppUser user)
        {
            IdentityResult result = await base.ValidateAsync(manager, user);
            List<IdentityError> errors = new List<IdentityError>();
            if (manager == null)
            {
                throw new ArgumentNullException(nameof(manager));
            }
            if (user == null)
            {
                throw new ArgumentNullException(nameof(user));
            }
            if (user.UserName == "google")
            {
                errors.Add(new IdentityError{  
                    Code= "googlekey",
                    Description = "Google cannot be used as a user name"
                });
            }
          
            return  errors.Count == 0 ? IdentityResult.Success : 
                IdentityResult.Failed(errors.ToArray());
           
        }
                 
    }

Similarly, we can write password-validating class PasswordCustomPolicy : PasswordValidator<AppUser> like example below

public class PasswordCustomPolicy : 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
{
Code="sameusername",
Description = "Username not allowed in password"
});
}
if (password.Contains("123"))
{
errors.Add(new IdentityError
     {
                    Code="123pass",
                    Description = "123 numeric sequence not allowed in password"
                });
            }
            if (password.Contains("xyz") ||
                password.Contains("abc"))
            {
                errors.Add(new IdentityError
                {
                    Code="abcpass",
                    Description = "xyz or abc not allowed in password"
                });
            }
            return errors.Count == 0 ? IdentityResult.Success : IdentityResult.Failed(errors.ToArray());
        }
    }
Register custom validation class

Now, let’s look at how to configure above two custom classes in startup file.

services.AddIdentity<AppUser, IdentityRole>(options =>
{
	options.SignIn.RequireConfirmedAccount = true;
})
.AddEntityFrameworkStores<AppIdentityDbContext>()
	.AddTokenProvider<DataProtectorTokenProvider<AppUser>>
		(TokenOptions.DefaultProvider)
 .AddUserValidator<UsernameCustomPolicy>()
.AddPasswordValidator<PasswordCustomPolicy<AppUser>>();


services.AddTransient<IPasswordValidator<AppUser>, PasswordCustomPolicy<AppUser>>();
services.AddTransient<ICustomUserValidator<AppUser>, UsernameCustomPolicy>();

As you can see in above code .AddUserValidator<UsernameCustomPolicy>() .AddPasswordValidator<PasswordCustomPolicy>();, Using extension methods we can register custom validate class in startup file.

Calling ValidateAsync methods in controller

We have created custom user validation and password validation class, and then registered those classes in startup.cs file, now the time to test those custom ValidateAsync methods from controller to check if our custom business logic is working properly.

First, create the instance of UserValidator and PasswordValidator using dependency injection in constructor like code below.

private UserManager<AppUser> _userManager;
private SignInManager<AppUser> _signInManager;
private IUserValidator<AppUser> _userValidator;
private IPasswordValidator<AppUser> _passwordValidator;
public authController(ILogger<authController> logger,
UserManager<AppUser> userMgr, 
SignInManager<AppUser> signinMgr,
IUserValidator<AppUser> userValidator,
IPasswordValidator<AppUser> passwordValidator)
{
	_logger = logger;
	_userManager = userMgr;
	_signInManager = signinMgr;
	_userValidator = userValidator;
	_passwordValidator = passwordValidator;
}

Now call the ValidateAsync method using _userValidator.ValidateAsync in controller method like example below.

IActionResult register(UserModel model)
{
    AppUser user=new AppUser();
	Task<IdentityResult> _validaUser=  _userValidator.ValidateAsync(_userManager, user);
	if (_validaUser.Result.Succeeded)
	{
		var _userCreated = _userManager.CreateAsync(user, passwordString);
	}
	else
	{
		// add identity errors to model errors
		foreach (var err in _validaUser.Result.Errors)
		{
			ModelState.AddModelError(err.Code, err.Description);
		}
	}
}
Asp.Net Core C# Examples | Join Asp.Net MVC Course