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.
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.
If you are not familiar with indentify framework, read how to setup indentify framework in asp.net core application.
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.
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.
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()); } }
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.
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); } } }
You may be interested to read following posts