With ASP.NET Core Identity fully registered we can learn how to perform user registration actions in our project.
User registration is the process of registering users in our application by saving their credentials in the database. So, in this article, we are going to learn how to implement user registration actions in our project. Additionally, we are going to learn about different identity options that could help us in the process.
To navigate through the entire series, visit the ASP.NET Core Identity series page.
So, let’s get started.
VIDEO: User Registration with ASP.NET Core Web API and Identity.
Preparation for User Registration
Let’s begin with the UserRegistrationModel.cs class:
public class UserRegistrationModel { public string FirstName { get; set; } public string LastName { get; set; } [Required(ErrorMessage = "Email is required")] [EmailAddress] public string Email { get; set; } [Required(ErrorMessage = "Password is required")] [DataType(DataType.Password)] public string Password { get; set; } [DataType(DataType.Password)] [Compare("Password", ErrorMessage = "The password and confirmation password do not match.")] public string ConfirmPassword { get; set; } }
In this class, we have a couple of properties the user populates in the registration form. As you can see the Email
and Password
properties are required and the ConfirmPassword
property must match the Password
property.
With this in place, let’s create a new MVC controller with two actions inside:
public class AccountController : Controller { [HttpGet] public IActionResult Register() { return View(); } [HttpPost] [ValidateAntiForgeryToken] public IActionResult Register(UserRegistrationModel userModel) { return View(); } }
So, we have the Account
controller with two Register
actions (GET and POST). We are going to use the first one to show the view and the second one for the user registration logic.
With that in place, let’s create a view for the GET Register action:
@model IdentityByExamples.Models.UserRegistrationModel <h1>Register</h1> <h4>UserRegistrationModel</h4> <hr /> <div class="row"> <div class="col-md-4"> <form asp-action="Register"> <div asp-validation-summary="All" class="text-danger"></div> <div class="form-group"> <label asp-for="FirstName" class="control-label"></label> <input asp-for="FirstName" class="form-control" /> <span asp-validation-for="FirstName" class="text-danger"></span> </div> <div class="form-group"> <label asp-for="LastName" class="control-label"></label> <input asp-for="LastName" class="form-control" /> <span asp-validation-for="LastName" class="text-danger"></span> </div> <div class="form-group"> <label asp-for="Email" class="control-label"></label> <input asp-for="Email" class="form-control" /> <span asp-validation-for="Email" class="text-danger"></span> </div> <div class="form-group"> <label asp-for="Password" class="control-label"></label> <input asp-for="Password" class="form-control" /> <span asp-validation-for="Password" class="text-danger"></span> </div> <div class="form-group"> <label asp-for="ConfirmPassword" class="control-label"></label> <input asp-for="ConfirmPassword" class="form-control" /> <span asp-validation-for="ConfirmPassword" class="text-danger"></span> </div> <div class="form-group"> <input type="submit" value="Create" class="btn btn-primary" /> </div> </form> </div> </div> @section Scripts { @{await Html.RenderPartialAsync("_ValidationScriptsPartial");} }
So basically, we have all the input fields from our model in this view. Of course, clicking the Create button will direct us to the POST Register method with the UserRegistrationModel
populated.
Now, let’s install the AutoMapper library, so we could map the UserRegistrationModel
to the User
class:
PM> Install-Package AutoMapper.Extensions.Microsoft.DependencyInjection
After we execute this command, AutoMapper will be installed. We are going to use it in a moment.
Finally, let’s slightly modify the _Layout
view:
<div class="navbar-collapse collapse d-sm-inline-flex flex-sm-row-reverse"> <partial name="_LoginPartial" /> <ul class="navbar-nav flex-grow-1"> <li class="nav-item"> <a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Index">Home</a> </li> ...
We’ve added a partial view, but we have to create it as well. To do that, let’s create a new _LoginPartial
view file in the Shared
folder:
<ul class="navbar-nav"> <li class="nav-item"> <a class="nav-link text-dark" asp-controller="Account" asp-action="Register">Register</a> </li> </ul>
Excellent.
If we start our application, we are going to see the Register
link in the upper-right corner. After we click on it, the Register
view will appear.
User Registration in Action
To start with the user registration logic, we have to inject our AutoMapper and UserManager class in the Account controller:
private readonly IMapper _mapper; private readonly UserManager<User> _userManager; public AccountController(IMapper mapper, UserManager<User> userManager) { _mapper = mapper; _userManager = userManager; }
The UserManager
class comes from the Microsoft.AspNetCore.Identity
namespace and it provides a set of helper methods to help us manage a user in our application.
Before we continue, let’s register AutoMapper in the ConfigureServices
method in the Startup
class:
services.AddAutoMapper(typeof(Startup));
If you are using .NET 6 or later, the code is a bit different because we have to modify the Program class:
builder.Services.AddAutoMapper(typeof(Program));
Now, we can modify the Register
action:
[HttpPost] [ValidateAntiForgeryToken] public async Task<IActionResult> Register(UserRegistrationModel userModel) { if(!ModelState.IsValid) { return View(userModel); } var user = _mapper.Map<User>(userModel); var result = await _userManager.CreateAsync(user, userModel.Password); if(!result.Succeeded) { foreach (var error in result.Errors) { ModelState.TryAddModelError(error.Code, error.Description); } return View(userModel); } await _userManager.AddToRoleAsync(user, "Visitor"); return RedirectToAction(nameof(HomeController.Index), "Home"); }
As you can see, the action is async now because the UserManager’s helper methods are async as well. Inside the action, we check for the model validity and if it is invalid, we just return the same view with the invalid model.
If that check passes, we map the registration model to the user.
Additionally, we use the CreateAsync
method to register the user. But this method does more for us. It hashes a password, executes an additional user checks and returns a result.
If the result is successful, we just attach the default role to the user, again with the UserManager’s help, and redirect the user to the Index page.
But if the registration fails, we loop through all the errors and add them to the ModelState
.
Finally, we have to create the MappingProfile
class:
public class MappingProfile : Profile { public MappingProfile() { CreateMap<UserRegistrationModel, User>() .ForMember(u => u.UserName, opt => opt.MapFrom(x => x.Email)); } }
You can see that we map email to the username because we are not using the username in the registration form.
Testing
Let’s test several invalid cases:
You can see the model validation is working just fine. The CreateAsync
method validates the password-related errors too.
If a user enters valid values in our form, the user registration process will complete successfully and they will be redirected to the Index page:
Excellent.
Now, let’s dive a bit into the ASP.NET Core Identity configuration.
Identity Options
As you could see in the testing example, our user registration process requires a password to fulfill certain rules. These rules can be found and adjusted in the IdentityOptions
settings.
Right now, the password requires at least 6 characters, upper and lower case letters, etc.
Let’s play around a bit with these values.
We are going to modify the AddIdentity
method in the ConfigureServices
method for .NET 5 and previous versions:
services.AddIdentity<User, IdentityRole>(opt => { opt.Password.RequiredLength = 7; opt.Password.RequireDigit = false; opt.Password.RequireUppercase = false; }) .AddEntityFrameworkStores<ApplicationContext>();
In .NET 6 and later, we have to modify the Program class:
builder.Services.AddIdentity<User, IdentityRole>(opt => { opt.Password.RequiredLength = 7; opt.Password.RequireDigit = false; opt.Password.RequireUppercase = false; }) .AddEntityFrameworkStores<ApplicationContext>();
So, we override the default password configuration by setting the required password length to 7 instead of 6 characters, which is the default value. Additionally, we state that digits and upper case letters are not required.
If we test this with the „test“ password:
We can see that there are no more alerts for digits and uppercase letters, and the required length is now 7 characters.
In our example, we are using an email as a user name. By default, email is not required to be unique, but we can change that:
opt.Password.RequiredLength = 7; opt.Password.RequireDigit = false; opt.Password.RequireUppercase = false; opt.User.RequireUniqueEmail = true;
If we try to register with an already registered email address:
Of course, pay attention that showing a message like this to a user could be a potential security risk.
That’s because, if this is a user who tries to hack our account, we have just reviled this email that already exists in our system and allowed them to focus only on the passwords.
A better solution would be to send an email message to the owner of this account, with the information that the account already exists.
By doing this, we don’t narrow down hacking possibilities for the malicious user and our regular user could proactively change the password or contact the system administrator to report a possible account breach.
Conclusion
There are a lot more settings we can play with, but for now, this is enough.
In articles to come, we are going to modify IdentityOptions settings even more. But feel free to explore it by yourself. As you can see, the names are pretty self-explanatory.
In the next article, we are going to learn about Login and Logout functionalities with ASP.NET Core Identity.
Until then…
.net 6.0 не работает. User not found.
builder.Services.AddIdentity<User, IdentityRole>(opt =>
{
opt.Password.RequiredLength = 7;
opt.Password.RequireDigit = false;
opt.Password.RequireUppercase = false;
})
.AddEntityFrameworkStores<ApplicationContext>();
Just download the source code. It works in .NET 6 as well. It was tested. I am not even sure why do you get user not found error for the registration action. If you registering a user, they shouldn’t exist in the db. Also the code you posted has nothing to do with the business logic. These are just rules that must be fulfilled imto register a new user.
Hi Marinko,
If I can mention a couple things that slowed me down a bit: when you create the view in the ‘Preparation for User Registration’, it might be helpful to indicate the file name (which is not UserRegistrationModel.cshtml :-), in the ‘User Registration in Action’ section you say to register the AutoMapper service to the `Configuration` method – it should be `ConfigureServices`
An excellent article – it’s very rewarding working through the series. Kudos.
Hi Mcalex. Thank you for the suggestions, there are fixed now. I hope you will learn a lot from this series. Best regards.
Thanks a lot for this tutorials. when the articles on remaining owasp vulnerabilities will be updated.
Thank you Archana. We will see about that. We had to stop it due to some other projects on our site, but we will continue that series. I can’t tell when, but will be for sure.
Thanks. Waiting for remaining articles.
Wonderful article man! Thanks! Just a question: when user is being registered, I dont see any ‘await HttpContext.SignInAsync’ call. Is this called automatically by ASP.NET Identity?
Thanks
Hello Luke. Thank you very much for reading this article and I am glad you liked it.
No, I am not using the SignInAsync here, because with the Registratio action I want only to create a new user. But if you read the next article in the series, you are going to see the usage of the HttpContext.SignInAsync method and also which method does this for you from ASP.NET Core Identity (PasswordSignInAsync).
Beatiful .net identity series. I’m still following your series, they are amazing. Thanks for sharing.
Thank you Erick for reading. It means a lot to us to receive that kind of feedback from our readers.
thank you for your great series about Identity in asp.net core.
I am new to asp.net core and came from java world to this stack
my question is where is a clear explanation of asp.net core on Microsoft website ?
Link
I found this link is about customizing a user and registration process but it was not clear and without any comment in the codes and just talked about Identity in Razor pages and did not talk about identity in MVC and token process in Web Api .
where can I find more official resources and explanations of this topic ?
thank you
Hello. Well, I am not sure about the official documentation. But, when you say “a clear explanation”, we have a lot articles related to ASP.NET Core MVC and WEB Api. Additionally, this ASP.NET Core Identity series is pretty detailed and we are about to publish the IdentityServer4 series as well. I am sure there are a lot of good resources online as well, but I am just not sure about the official documentation. You can go through our series and see if those are helpful to you. https://code-maze.com/net-core-series/ https://code-maze.com/asp-net-core-mvc-series/
thank you for your great series about Identity in asp.net core.
I am new to asp.net core and came from java world to this stack
my question is where is a clear explanation of asp.net core on Microsoft website ?
Link
I found this link is about customizing a user and registration process but it was not clear and without any comment in the codes and just talked about Identity in Razor pages and did not talk about identity in MVC and token process in Web Api .
where can I find more official resources and explanations of this topic ?
thank you
User class can be found in example code.
Thanks for it!
Hello Diego. You are right this class can be found in the source code. This article is part of the Identity series. If you inspect the previous article you will find the User class created there. So, it is in the article as well, just not this one 🙂