Home » ASP » ASP.MVC » Asp.Net MVC Login with email instead of UserName with Identity

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

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.
AspNetUserEmailProperty

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!

If you like my article, think to buy my annual book, professionally edited by a proofreader. directly from me or on Amazon. I also wrote a TypeScript book called Holistic TypeScript

9 Responses so far.

  1. Andrey P. says:

    Nice and easy solution, thx 🙂

  2. Jason says:

    One note, you can’t guarantee that the username generated is unique. Bob@bob.com = Bobb@ob.com, or johnsmith@google.com = john.smith@google.com.

    Thus, when you login you can’t use the default UserManager.FindAsync(username, password) method.

    I think you’d need to login in two steps, first call UserManager.FindByEmailAsync(model.Email), and then verifiy the password with UserManager.CheckPasswordAsync(user, password) [Or UserManager.VerifyPasswordAsync(IUserPasswordStore store, user, password) if you’ve implemented your own passwordStore].

    • You are absolutly right. I will edit this post. Thank you for your input.

      • Jason says:

        My note above requires two round trips to the database, once to find the user and again to verify the password. If you’re using Identity without Entity Framework and have implemented your own IUserStore (as shown here: http://www.asp.net/identity/overview/extensibility/implementing-a-custom-mysql-aspnet-identity-storage-provider ), then you can do it in one trip.

        In AccountController.cs replace:
        if (newUser != null){
        await SignInAsync(newUser, model.RememberMe);
        return RedirectToLocal(returnUrl);
        }
        with:
        if (UserManager.CheckPassword(newUser, model.Password)){
        await SignInAsync(newUser, model.RememberMe);
        return RedirectToLocal(returnUrl);

        Then in UserStore implementation something like:
        public Task GetPasswordHashAsync(IdentityUser user){
        string passwordHash = userTable.LoginByEmail(user);
        return Task.FromResult(passwordHash);
        }

        This passes an (empty) instance of your implementation of Iuser (I called mine IdentityUser) to your data access class. Fill in the data and returns the passwordHash to Identity’s UserManager class, which does it’s thing behind the scenes and returns true if the hashes match, and your IUser class is filled in already.

  3. Matt says:

    Are you looking for something like AntiForgeryConfig.UniqueClaimTypeIdentifier = ClaimTypes.Name; (which I set in the OWIN startup class)? Sorry if I’m misunderstanding.

  4. Oscar says:

    I think this is an unnecessary workaround. The real problem is that by default User name is not allowed to have special characters. To get around this all you need to do is add the following line to AccountController constructor below UserManager = userManager;
    UserManager.UserValidator = new UserValidator(UserManager) { AllowOnlyAlphanumericUserNames = false };
    After doing this you only need to change the Display name in the model and your user name can be email addresses.

  5. M Levesque says:

    If a user tries to sign up with a used email address, how do you prevent the ‘username already in use’ portion of the error message? When trying to create the user, UserManager returns 2 messages: email already in use and username already in use.

    • At the top of my head. you can filter and display the message you want. In my case, when I wrote this article, I ended up to display a very generic message to not leak the information of having an email being used by the system. It’s been more than three years sorry.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.