(C# ASP.NET Core) Adding a Registration Page to an Identity based Authentication Project

The problem with the built-in registration page provided by ASP.NET Core Identity API is that it contains a lot of tags and bootstrap attributes that make it time consuming to integrate it into a project that doesn't use bootstrap or jQuery. The objective of this article is to explain how to create a neat and simple registration page that can be styled with any css and javascript of choice. We use a simple html table to create a form, but we will not use any css and client side validation because that can always be done with the web designer teams.
(Rev. 19-Mar-2024)

Categories | About |     |  

Parveen,

Table of Contents (top down ↓)

Configure the Program.cs File

First, we have to make a few additional configuration to the program.cs file (see the code below, the completed visual studio 2022 project is in the downloads). So let's begin by opening the program.cs file.

Recall from the first tutorial of this series that we have already added services for identity, and also notice that we set a RequireConfirmedAccount option to true - which means that we will also have to integrate an email or sms based confirmation loop to a newly registered user - similar to what you see on various websites these days.

Also recall that we added options for the authentication cookie - when we specified the login and access denied paths.

Now we shall add IdentityOptions for password, lockout and user settings.

Password settings are used to specify various restrictions on a password. Lockout settings are used to set various lockout parameters, and as we can see, the user settings are used to configure various restrictions on user id or names.

// completed program.cs file 
// visual studio 2022 and later 
using Microsoft.AspNetCore.Authentication.Cookies;

using Microsoft.AspNetCore.Identity;

using MyRazorApp.Data;

var builder = WebApplication.CreateBuilder();

builder.Services.AddDbContext<MyAuthContext>();

// add identity 
builder.Services
.AddDefaultIdentity<IdentityUser>(
  options => options.SignIn.RequireConfirmedAccount = true
)
.AddEntityFrameworkStores<MyAuthContext>();

// authentication 
builder.Services
.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(options =>
{

  options.LoginPath = "/Auth/Login";

  options.AccessDeniedPath = "/Auth/AccessDenied";

  // Cookie settings 
  // prevent cookie from being accessed 
  // through javascript on the client side 
  options.Cookie.HttpOnly = true;

  options.ExpireTimeSpan = TimeSpan.FromMinutes(5);

  options.SlidingExpiration = true;

});

builder.Services.Configure<IdentityOptions>
(options =>
{

  // Password 
  options.Password.RequireUppercase = true;

  options.Password.RequireDigit = true;

  options.Password.RequireLowercase = true;

  options.Password.RequireNonAlphanumeric = true;

  options.Password.RequiredUniqueChars = 1;

  options.Password.RequiredLength = 6;

  // Lockout 
  options.Lockout.MaxFailedAccessAttempts = 5;

  options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(5);

  options.Lockout.AllowedForNewUsers = true;

  // User 
  options.User.AllowedUserNameCharacters =
  "abcde...xyzABC...XYZ012...789-._@+";

  options.User.RequireUniqueEmail = false;

});

builder.Services.AddRazorPages();

var app = builder.Build();

// create the database 
// these calls can be removed in 
// a production scenario 
// the recommended method is through migrations 
// as explained in the first tutorial of this chapter 
using (var scope = app.Services.CreateScope())

{

  var services = scope.ServiceProvider;

  var ctx = services.GetRequiredService<MyAuthContext> ();

  ctx.Database.EnsureCreated();

}

app.UseAuthentication();

app.UseAuthorization();

app.MapRazorPages();

app.Run();

Video Explanation (see it happen!)

Please watch the following youtube video:

Add the Register Razor Page

Next, we have to add a register page.

Open the solution explorer, and locate the folder for Auth area, and right click the Pages folder to add a razor page called Register.

Let us examine the Register.cshtml markup file.

First we have the various directives for page, model and addtagHelper

Then we have a tag for presenting a validation summary of various model errors.

A table tag has been used to create the form. The first tr is for the user id. This element is connected to a property called UserID.

The next tr is for the phone number. A phone number could be used for 2-step authentication later on. It could even have been omitted altogether. It is bound to a property called UserPhone.

Then we have a tr for password, which is connected to a property called UserPassword.

The next tr is for the confirm password. Notice that we haven't used any client side validations because they can always be added by the web designer teams.

Lastly, we have the submit button. The form shows the bare minimum html required for creating a registration form. It can be styled with any CSS - but that is out of the scope of this tutorial.


// Area->Auth->Pages->Register.cshtml 
  
@page

@model MyRazorApp.Areas.Auth.Pages.RegisterModel

@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

<h1>Please Register</h1>

<div asp-validation-summary="All"></div>

<form method="post">

  <table>

    <tr>
      <td>
        <label asp-for="UserID"></label>
      </td>
      <td>
        <input asp-for="UserID" />
      </td>
      <td>
        <span asp-validation-for="UserID"></span>
      </td>
    </tr>

    <tr>
      <td>
        <label asp-for="UserPhone"></label>
      </td>
      <td>
        +ISD-<input asp-for="UserPhone" />
      </td>
      <td>
        <span asp-validation-for="UserPhone"></span>
      </td>
    </tr>

    <tr>
      <td>
        <label asp-for="UserPassword"></label>
      </td>
      <td>
        <input asp-for="UserPassword" />
      </td>
      <td>
        <span asp-validation-for="UserPassword"></span>
      </td>
    </tr>

    <tr>
      <td>
        <label asp-for="ConfirmPassword"></label>
      </td>
      <td>
        <input asp-for="ConfirmPassword" />
      </td>
      <td>
        <span asp-validation-for="ConfirmPassword"></span>
      </td>
    </tr>

    <tr>
      <td colspan="2" style="text-align:right">
        <input type="submit" value="Create!" />
      </td>
    </tr>

  </table>

</form>

  

Now let us examine the programming side. Open the backing class for this markup page. This is the Register.cshtml.cs and it contains the RegisterModel class.

First we have the various namespace directives. The class has been marked as AllowAnonymous so that the page is publically available for anyone to register on our website.

First we have a BindProperty for UserID, then we have a property for the phone number. We have used a regular expression for a phone number of exactly ten digits.

// Area->Auth->Pages->Register.cshtml.cs 

// namespaces 
using Microsoft.AspNetCore.Authorization;

using Microsoft.AspNetCore.Identity;

using Microsoft.AspNetCore.Mvc;

using Microsoft.AspNetCore.Mvc.RazorPages;

using System.ComponentModel.DataAnnotations;

namespace MyRazorApp.Areas.Auth.Pages
{

  [AllowAnonymous]
  public class RegisterModel : PageModel
  {

    [BindProperty]
    [Required]
    [DataType(DataType.EmailAddress)]
    public String? UserID { get; set; }

    // Phone (can be optional, remove Required) 
    [BindProperty]
    [Required]
    [DataType(DataType.PhoneNumber)]
    [RegularExpression(@"^\d{10}$", ErrorMessage = "10 digits")]
    public String? UserPhone { get; set; }

    // Password 
    [BindProperty]
    [Required]
    [DataType(DataType.Password)]
    public String? UserPassword { get; set; }

    // confirm password 
    [BindProperty]
    [Required]
    [Compare("UserPassword", ErrorMessage = "Passwords do not match")]
    [DataType(DataType.Password)]
    public string? ConfirmPassword { get; set; }

    // UserManager service 
    private readonly UserManager<IdentityUser> _um;

    public RegisterModel(UserManager<IdentityUser> um)
    {

      _um = um;

    }

    public async Task<IActionResult> OnPostAsync()
    {

      if (!ModelState.IsValid)
      {

        return Page();

      }

      var user = new IdentityUser()
      {

        UserName = UserID,
        Email = UserID,
        PhoneNumber = UserPhone
      };

      var result = await _um.CreateAsync(user, UserPassword);

      if (!result.Succeeded)
      {

        foreach (var error in result.Errors)
        {

          ModelState.AddModelError(string.Empty, error.Description);

        }

      }

      else 
      {

        String message =
          "SUCCESS: Registration done! In the next tutorial " +
          "we add code for email confirmation because in our program.cs " +
          "file we have options.SignIn.RequireConfirmedAccount = true";

        // just using this for temporarily displaying 
        // the above message 
        ModelState.AddModelError(string.Empty, message);

      }

      return Page();

    }

  }

}

Similarly, we have the properties for password and confirm password.

We have used constructor based dependency injection to obtain the UserManager service - which is one of the services that the Identity API provide us for performing user related actions like registration and login. The instance has been cached as a readonly member _um.

The OnPostAsync method executes when the user submits and posts the form.

An IdentityUser has been created on the basis of data entered by the user. The user is registered with a call to the CreateAsync method of the UserManager instance. Notice that the entire work has been done by this single call. It handles all the database communication for us.

The return value can be examined for success.

If the method doesn't succeed, the errors are added to the validation summary. These errors are such as - a user already exists.

If the method succeeds we need to add a further code for email confirmation because we have configured RequireConfirmedAccount in the program.cs file. We shall do that in the next tutorial; but for now we have temporarily added a success message to the validation summary. This is against the rules - but we have done it so that we know that we succeeded, and this will be removed in the next tutorial.

Run the Project

The completed source course as visual studio 2022 project is given in your downloads.

Run the project to open the home page of our website.

Click the link to attempt a visit to the restricted page. We are redirected to the login page. Click the link to register.

Enter any data. Enter a simple password such as 123 and click submit.

As expected, this password fails validation because we configured strict requirments in our program.cs file.

Next enter a complex password and hit the create button. The registration succeeds!

Verify the Creation

Finally, we should verify the creation by examining the users table in our SQL Server database.

Open SQL Server Object Explorer and locate the database myauthdb

Locate the table AspNetUsers and right click to view the data.

As you can see, we are able to verify that the user has been created!

In the next tutorial we shall add code for email based verification of the user. Thanks!


This Blog Post/Article "(C# ASP.NET Core) Adding a Registration Page to an Identity based Authentication Project" by Parveen is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.