Patrick Desjardins Blog
Patrick Desjardins picture from a conference

Asp.Net MVC Login with email instead of UserName with Identity

Posted on: 2014-03-04

I found very strange that it is not a simple task to login with an email in Asp.Net MVC. You cannot simply decide what property is the identifier. Asp.Net MVC decides for you that it is the UserName property.

Here is a the IdentityUser class from Microsoft.AspNet.Identity.EntityFramework.

 namespace Microsoft.AspNet.Identity.EntityFramework { public class IdentityUser : IUser { public virtual string Id { get; set; } public virtual string UserName { get; set; } public virtual string PasswordHash { get; set; } public virtual string SecurityStamp { get; set; } public virtual ICollection<IdentityUserRole> Roles { get; private set; } public virtual ICollection<IdentityUserClaim> Claims { get; private set; } public virtual ICollection<IdentityUserLogin> Logins { get; private set; } public IdentityUser() { this.Id = Guid.NewGuid().ToString(); this.Claims = (ICollection<IdentityUserClaim>) new List<IdentityUserClaim>(); this.Roles = (ICollection<IdentityUserRole>) new List<IdentityUserRole>(); this.Logins = (ICollection<IdentityUserLogin>) new List<IdentityUserLogin>(); } public IdentityUser(string userName): this() { this.UserName = userName; } } } 

The first step to use email is to add an email property to this class. Microsoft Identity team has not sealed the class, so it is possible to inherit from it and add you own property. This will extend what we can do with Identity.

public class ApplicationUser : IdentityUser { 
  public string Email { get; set; } 
} 

That's it for the model. Entity Framework will use this class instead of the default one if you define you DbContext with a special class.

public class MainDbContext : IdentityDbContext<ApplicationUser>{
  //...
} 

That is what is required. This create in the background a DbSet of your class that inherit from IdentityUser. I have called mine ApplicationUser, but you can use whatever you prefer. IdentityDbContext override the OnModelCreating. This is important that if you inherit from it that you call the base class to still have all the configuration provided by the IdentityDbContext. To be more detailed, the OnModelCreating associate the custom class to AspNetUsers table. It does plenty of others thing, but from what it concerns us right now, this is it. From here, if you instruct Entity Framework to build your database, you should see in the database your field in Identity table.

The next step is to change the View Model and the View. The View Model for the registration use an UserName property. We can remove this and add one for Email.

public class RegisterViewModel { 
  [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; }

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

This is the place where you can add additional data annotation to have more validation. Then, we need to change the view that use the Register View Model. We need to remove the username form group to add a new one for email.

<div class="form-group"> @Html.LabelFor(m => m.Email, new { @class = "col-md-2 control-label" }) <div class="col-md-10"> @Html.TextBoxFor(m => m.Email, new { @class = "form-control" }) </div> </div> 

The last step is to change the controller to do something with the email and username. Since Identity still use username and password combination than we have to trick the system.

First of all, we need to generate a user name from the email. It is not possible to use directly the email because it has some invalid characters like @. You can have a small method that transform the user name.

public string GenerateUserName(string email) { 
  return email.Replace("@", "").Replace(".", "").Replace("-", ""); 
} 

From here, you can create a new ApplicationUser and assign your generated user name into the property. The next code remains the same by calling the UserManager.

Second, we need to modify the login because user will enter an email and you need to transform it to user name. The login ViewModel needs to be changed.

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

  [Required] 
  [DataType(DataType.Password)] 
  [Display(Name = "Password")]
  public string Password { get; set; }

  [Display(Name = "Remember me?")] 
  public bool RememberMe { get; set; } 
} 

The view also change by not having input for an username but for an email. This is the same way we have done for the registration page. I will not put the code here to keep it simple.

Finally, the controller must be changed too. Not a lot of thing need to be changed but since we ask for an email and that the system use a username than we must convert it.

 var user = await userService.FindAsync(GenerateUserName(model.Email), model.Password); 

In conclusion, it requires some manipulation but it is not difficult. It would be cleaner not to manipulate the UserName property but it is still not a nightmare to proceed.

Concerning the user name

The algorithm that generate the user name is not strong. Collision can occur if you have the concatenation this way. This is not a huge deal since it will fail to save in the database but still a better way is to have something unique. To save character like @ as user name, the framework allows you to configure the UserManager to have not only alphanumeric characters.

public UserManager(IMainDbContext dbContext) { 
  var dbMainContext = dbContext.GetDbContext(); 
  this.userManager = new UserManager<ApplicationUser>(new UserStore<ApplicationUser>(dbMainContext));

  //Allow to have email in the username
  this.userManager.UserValidator = new UserValidator<ApplicationUser>(this.userManager) 
  { 
    AllowOnlyAlphanumericUserNames = false 
  }; 
} 

As you can see, you can set a new UserValidator and set the AllowOnlyAlphanumericUserNames to false. This is way better!