Introduction
Microsoft has released a new version of ASP.NET Identity, 2.0.0-alpha 1. You can refer to ASP.NET Identity Preview to get the idea of this release.
In that context, we are here today to create a MVC application using ASP.NET Identity to customize the password to provide better security to the application. You'll learn here to modify the minimum length of the password and to make the entering of numeric and special characters in the password mandatory and disallow recently used passwords.
By default the ASP.NET Identity requires that the minimum length of the password is 6 characters and here we change it to 8. We'll work on the Visual Studio 2013 version and you must update all the assemblies related to the ASP.NET Identity.
So, let's proceed with the following sections:
- Create ASP.NET MVC Application
- Modifying the Minimum Length
- Customize Password Validation
Create ASP.NET MVC Application
Now we create the MVC application using the following procedure.
Step 1: Open Visual Studio 2013 and click on "New Project".
Step 2: Select the MVC Project Template to create the application and click "OK".
Step 3: In Solution Explorer, right-click on References and click "Manage NuGet Packages...".
Step 4: Select "Include Prerelease" and search for "Identity". Update all the Identity related packages.
After this you can see in your Pacakages.Config that all the packages are updated. We can also look at the following points to understand how the user is created in the application. You can see the following code in your AccountController class under the Controllers folder:
public async Task<ActionResult> Register(RegisterViewModel model)
{
if (ModelState.IsValid)
{
var user = new ApplicationUser() { UserName = model.UserName };
var result = await UserManager.CreateAsync(user, model.Password);
if (result.Succeeded)
{
await SignInAsync(user, isPersistent: false);
return RedirectToAction("Index", "Home");
}
else
{
AddErrors(result);
}
}
// If we got this far, something failed, redisplay form
return View(model);
}
- The Account controller uses an instance of the UserManager class. This takes a UserStore class that has persistence-specific API for user management.
- The preceding method creates a new user. The call to CreateAsync in turn calls the ValidateAsync on the PasswordValidator property for the password validation.
Modifying the Minimum Length
Now in this section we'll modify the minimum length of the password to 8. The PasswordValidator property is used to set the length, this value can be changed. Proceed with the following procedure.
Step 1: Add a new folder named IdentityExtentions to the project.
Step 2: Add a new class named AppUserManager in this folder.
Step 3: Replace the class code with the code below.
First add the following assemblies:
using CustomizePasswordApp.Models;
using Microsoft.AspNet.Identity;
using Microsoft.AspNet.Identity.EntityFramework
Code:
namespace CustomizePasswordApp.IdentityExtentions
{
public class AppUserManager : UserManager<ApplicationUser>
{
public AppUserManager()
: base(new UserStore<ApplicationUser>(new ApplicationDbContext()))
{
PasswordValidator = new MinimumLengthValidator(8);
}
}
}
Step 4: Now we need to change the AccountController to use the AppUserManager class . Change the constructor code and class property with the highlighted code below:
public class AccountController : Controller
{
public AccountController()
: this(new AppUserManager())
{
}
public AccountController(AppUserManager userManager)
{
UserManager = userManager;
}
public AppUserManager UserManager { get; private set; }
Step 5: Build the solution and run the application. Now try to register a new user. If the password length is less then 8, you'll get the error as shown below:
Customize Password Validation
As mentioned above now we'll customize the password in this section. Use the following procedure to do that.
Password must have one special and one numeric character
Step 1: Add a new class named CustomizePasswordValidation in the IdentityExtensions folder.
Step 2: Replace the code with the code below:
using Microsoft.AspNet.Identity;
using System;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
namespace CustomizePasswordApp.IdentityExtentions
{
public class CustomizePasswordValidation : IIdentityValidator<string>
{
public int LengthRequired { get; set; }
public CustomizePasswordValidation(int length)
{
LengthRequired = length;
}
public Task<IdentityResult> ValidateAsync(string Item)
{
if (String.IsNullOrEmpty(Item) || Item.Length < LengthRequired)
{
return Task.FromResult(IdentityResult.Failed(String.Format("Minimum Password Length Required is:{0}", LengthRequired)));
}
string PasswordPattern = @"^(?=.*[0-9])(?=.*[!@#$%^&*])[0-9a-zA-Z!@#$%^&*0-9]{10,}$";
if (!Regex.IsMatch(Item, PasswordPattern))
{
return Task.FromResult(IdentityResult.Failed(String.Format("The Password must have at least one numeric and one special character")));
}
return Task.FromResult(IdentityResult.Success);
}
}
}
In the code above, the ValidateAsync method is used to check the password length at first and then by the regular expression we can check whether or not the password entered satisfys the requirements.
Step 3: Now we need to provide this validator in the PasswordValidator property of the UserManager. Open the AppUserManager class and change the PasswordValidator property as shown below:
public class AppUserManager : UserManager<ApplicationUser>
{
public AppUserManager()
: base(new UserStore<ApplicationUser>(new ApplicationDbContext()))
{
PasswordValidator = new CustomizePasswordValidation(8);
}
}
Step 4: Run the application and register a new user that does not meet the requirements.
Password cannot be reusable
While changing the password, we can prevent the user from entering the previous password. Use the following procedure to do that.
Step 1: Open the IdentityModels.cs in the Models folder and create a new class named UsedPassword and change the code with the highlighted code as shown below:
using Microsoft.AspNet.Identity.EntityFramework;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace CustomizePasswordApp.Models
{
public class ApplicationUser : IdentityUser
{
public ApplicationUser()
: base()
{
UserUsedPassword = new List<UsedPassword>();
}
public virtual IList<UsedPassword> UserUsedPassword { get; set; }
}
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public ApplicationDbContext()
: base("DefaultConnection")
{
}
}
public class UsedPassword
{
public UsedPassword()
{
CreatedDate = DateTimeOffset.Now;
}
[Key, Column(Order = 0)]
public string HashPassword { get; set; }
public DateTimeOffset CreatedDate { get; set; }
[Key, Column(Order = 1)]
public string UserID { get; set; }
public virtual ApplicationUser AppUser { get; set; }
}
}
In the preceding we used the UserUsedPassword property to hold the list of used passwords.
Step 2: Now we need to store the password hash in the history table when the user is created for the first time. We need to override the existing UserStore now because since the UserStore does not define such a method separately to store the password history. So, add a new class in the IdentityExtensions folder named AppUserStore and add the following code:
using CustomizePasswordApp.Models;
using Microsoft.AspNet.Identity.EntityFramework;
using System.Data.Entity;
using System.Threading.Tasks;
namespace CustomizePasswordApp.IdentityExtentions
{
public class AppUserStore : UserStore<ApplicationUser>
{
public AppUserStore(DbContext MyDbContext)
: base(MyDbContext)
{
}
public override async Task CreateAsync(ApplicationUser appuser)
{
await base.CreateAsync(appuser);
await AddToUsedPasswordAsync(appuser, appuser.PasswordHash);
}
public Task AddToUsedPasswordAsync(ApplicationUser appuser, string userpassword)
{
appuser.UserUsedPassword.Add(new UsedPassword() { UserID = appuser.Id, HashPassword = userpassword });
return UpdateAsync(appuser);
}
}
}
In the code above The AddToUsedPasswordAsync() method stores the password in the UsedPassword table separately.
Step 3: Now we need to override the ChangePassword and ResetPassword methods in the AppUserManager class with the following code:
public class AppUserManager : UserManager<ApplicationUser>
{
private const int UsedPasswordLimit = 3;
public AppUserManager()
: base(new AppUserStore(new ApplicationDbContext()))
{
PasswordValidator = new CustomizePasswordValidation(8);
}
public override async Task<IdentityResult> ChangePasswordAsync(string UserID, string CurrentPassword, string NewPassword)
{
if (await IsPreviousPassword(UserID, NewPassword))
{
return await Task.FromResult(IdentityResult.Failed("You Cannot Reuse Previous Password"));
}
var Result = await base.ChangePasswordAsync(UserID, CurrentPassword, NewPassword);
if (Result.Succeeded)
{
var appStore = Store as AppUserStore;
await appStore.AddToUsedPasswordAsync(await FindByIdAsync(UserID), PasswordHasher.HashPassword(NewPassword));
}
return Result;
}
public override async Task<IdentityResult> ResetPasswordAsync(string UserID, string UsedToken, string NewPassword)
{
if (await IsPreviousPassword(UserID, NewPassword))
{
return await Task.FromResult(IdentityResult.Failed("You Cannot Reuse Previous Password"));
}
var Result = await base.ResetPasswordAsync(UserID, UsedToken, NewPassword);
if (Result.Succeeded)
{
var appStore = Store as AppUserStore;
await appStore.AddToUsedPasswordAsync(await FindByIdAsync(UserID), PasswordHasher.HashPassword(NewPassword));
}
return Result;
}
private async Task<bool> IsPreviousPassword(string UserID, string NewPassword)
{
var User = await FindByIdAsync(UserID);
if(User.UserUsedPassword.OrderByDescending(up=>up.CreatedDate).Select(up=>up.HashPassword).Take(UsedPasswordLimit).Where(up=>PasswordHasher.VerifyHashedPassword(up, NewPassword) != PasswordVerificationResult.Failed).Any())
{
return true;
}
return false;
}
Step 4: Now register the local user as specified by the validation requirements.
Step 5: Change the password using the same password when the user is created. You'll see the error message as shown below:
Summary
This article described how to customize the validation using the ASP.NET Identity and apply various policies in the application. Thanks for reading.