ASP.NET Identity 2.0 in MVC 5

First question when you want to create a simple web site is "Should I use existing account managment or should I create one by myself. Until the release of ASP.NET Identity I used ASP.NET membership and ASP.NET simple membership. I must say membership was too complicated for a simple web site. I didn't need roles as well as most of things membership offered. After a while they released simple membership which was meant to be as more "simple" membership but still in my opinion not very useful. The problem was also that both providers worked only with MsSql. In the latest web site I used ASP.NET Identity and I must say after some problems at the beginning it was right decision to use it. The major difference is that identity uses Entity Framework to communicate with database. This means that you can use other databases and not only MsSql.

MVC 5 web site with Identity system and email verification

I needed to create account managment in asp.net mvc5 web site that uses following features:

  • User registration with email (not username)
  • Verification of provided email
  • User phone number

First I tried to manage if it is possible to create user without username and only with email. Since email is unique and I did not see any need that users should use username. Eventually username is unique identifier in users table so I came with a solution that email is both username and email field in table. Below is example of my register model and registration of new users in identity.

View model:

public class RegisterViewModel {
    [Required]
    [EmailAddress]
    [Display(Name = "Email")]
    public string Email { get; set; }

    [DataType(DataType.PhoneNumber)]
    public string Mobile { get; set; }

    [Required]
    [StringLength(100, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 6)]
    [DataType(DataType.Password)]
    [Display(Name = "Password")]
    public string Password { get; set; }

    [DataType(DataType.Password)]
    [Display(Name = "Confirm password")]
    [Compare("Password", ErrorMessage = "The password and confirmation password do not match.")]
    public string ConfirmPassword { get; set; }
}

In register method we use User manager as shown below:

var user = new ApplicationUser {Email = model.Email, UserName = model.Email, PhoneNumber = model.Mobile};
var result = await UserManager.CreateAsync(user, model.Password);

As you can see phone number is also included in identity system so you do not have to manualy create field in ApplicationUser. I simply added Mobile property in RegiserViewModel so user can provide phone number (it is not a required field). Next feature was to confirm user email. To achieve this I have created custom UserManager called ApplicationUserManager.

public class ApplicationUserManager : UserManager<ApplicationUser>
{
    public ApplicationUserManager(IUserStore<ApplicationUser> store) : base(store){ }

    public static ApplicationUserManager Create(IdentityFactoryOptions<ApplicationUserManager> options, IOwinContext context)
    {
        var manager = new ApplicationUserManager(new UserStore<ApplicationUser>(context.Get<ApplicationDbContext>()));

        // Configure validation logic for usernames
        manager.UserValidator = new UserValidator<ApplicationUser>(manager)
        {
            AllowOnlyAlphanumericUserNames = false,
            RequireUniqueEmail = true,
        };

        // Configure validation logic for passwords
        manager.PasswordValidator = new PasswordValidator
        {
            RequiredLength = 6,
            RequireNonLetterOrDigit = false,
            RequireDigit = true,
            RequireLowercase = true,
            RequireUppercase = true,
        };

        // Configure user lockout defaults
        manager.UserLockoutEnabledByDefault = true;
        manager.DefaultAccountLockoutTimeSpan = TimeSpan.FromMinutes(5);
        manager.MaxFailedAccessAttemptsBeforeLockout = 5;

        manager.EmailService = new EmailService();

        var dataProtectionProvider = options.DataProtectionProvider;
        if (dataProtectionProvider != null)
        {
            manager.UserTokenProvider = new DataProtectorTokenProvider<ApplicationUser>(dataProtectionProvider.Create("ASP.NET Identity"));
        }
        return manager;
    }
}

I also created my EmailService to provide data for sending email:

public class EmailService : IIdentityMessageService
{
    public Task SendAsync(IdentityMessage message)
    {
        var email = new MailMessage(Convert.ToString(ConfigurationManager.AppSettings["FromMailAddress"]), message.Destination)
        {
            Subject = message.Subject,
            Body = message.Body,
            IsBodyHtml = true
        };

        var mailClient = new SmtpClient(
            Convert.ToString(ConfigurationManager.AppSettings["Host"]),
            Convert.ToInt32(ConfigurationManager.AppSettings["EmailPort"]))
        {
            Credentials =
                new NetworkCredential(
                    Convert.ToString(ConfigurationManager.AppSettings["EmailUsername"]),
                    Convert.ToString(ConfigurationManager.AppSettings["EmailPassword"])),
            EnableSsl = true
        };

        return mailClient.SendMailAsync(email);
    }
}

After creating custom UserManager it's time to create verification email. This is done after the creation of new user as shown below:

var user = new ApplicationUser { UserName = model.Email, Email = model.Email };
var result = await UserManager.CreateAsync(user, model.Password);
if (result.Succeeded)
{
    var code = await UserManager.GenerateEmailConfirmationTokenAsync(user.Id);
    var callbackUrl = Url.Action("ConfirmEmail", "Account", new { userId = user.Id, code = code }, protocol: Request.Url.Scheme);
    await UserManager.SendEmailAsync(user.Id, "Confirm your account", "Please confirm your account by clicking this link: <a href=\"" + callbackUrl + "\">link</a>");
}

Now we only have to check userId and code.

    [AllowAnonymous]
    public async Task<ActionResult> ConfirmEmail(string userId, string code)
    {
        if (userId == null || code == null)
        {
            return View("Error");
        }
        var result = await UserManager.ConfirmEmailAsync(userId, code);
        if (result.Succeeded)
        {
            var user = await UserManager.FindByIdAsync(userId);
            await SignInHelper.SignInAsync(user, isPersistent: false);
            return RedirectToAction("ConfirmEmailResponse");
        }
        return View("Error");
    }

NOTE: UserManager by default does not check if a user has verified password. So it is up to you that you check this before a user can login!

Example how to implement more advanced SignIn helper:

public async Task<SignInStatus> PasswordSignIn(string userName, string password, bool isPersistent, bool shouldLockout)
    {
        var user = await UserManager.FindByNameAsync(userName);
        if (user == null)
        {
            return SignInStatus.Failure;
        }
        if (await UserManager.IsLockedOutAsync(user.Id))
        {
            return SignInStatus.LockedOut;
        }
        if (!await UserManager.IsEmailConfirmedAsync(user.Id))
        {
            return SignInStatus.EmailNotConfirmed;
        }
        if (await UserManager.CheckPasswordAsync(user, password))
        {
            await SignInAsync(user, isPersistent);
            return SignInStatus.Success;
        }
        if (shouldLockout)
        {
            await UserManager.AccessFailedAsync(user.Id);
            if (await UserManager.IsLockedOutAsync(user.Id))
            {
                return SignInStatus.LockedOut;
            }
        }
        return SignInStatus.Failure;
    }

You can learn more about ASP.NET Identity on this page:

http://www.asp.net/identity

An demo example with more features can be found on this page:

http://blogs.msdn.com/b/webdev/archive/2014/02/18/adding-two-factor-authentication-to-an-application-using-asp-net-identity.aspx

From this example I have created my account manager. This example also shows two factor providers which I did not use and phone verification.

Conclusion

ASP.NET Identity 2.0 is already included by default in MVC5 project. By default project template it provides user login with local password and easy integration with external SignIn options like (Facebook, Gmail, Hotmail).

Pros:

  • database independent
  • extensible
  • simple