Verify Phone Number in ASP.NET Core Identity

Verify Phone Number in ASP.NET Core Identity

In this article, I will explain how to add and verify a Phone Number in ASP.NET Core Identity by sending an SMS with a verification code to the phone number. Please read our previous article discussing How to Send SMS in ASP.NET Core Identity. Let us proceed and see how to add a Phone Number and Verify the Phone Number in ASP.NET Core Identity:

Modifying the RegisterViewModel

The RegisterViewModel is the model used for registering the user. So, in this view model, we need to add a property to hold the Phone Number.

using Microsoft.AspNetCore.Mvc;
using System.ComponentModel.DataAnnotations;
namespace ASPNETCoreIdentityDemo.Models
{
    public class RegisterViewModel
    {
        [Required(ErrorMessage ="First Name is Required")]
        [Display(Name = "First Name")]
        public string FirstName { get; set; }

        [Display(Name = "Last Name")]
        public string? LastName { get; set; }

        [Required]
        [EmailAddress]
        [Remote(action:"IsEmailAvailable", controller: "Account")]
        public string Email { get; set; }

        [Phone]
        [Display(Name = "Phone Number")]
        public string PhoneNumber { get; set; }

        [Required]
        [DataType(DataType.Password)]
        public string Password { get; set; }

        [DataType(DataType.Password)]
        [Display(Name = "Confirm password")]
        [Compare("Password", ErrorMessage = "Password and confirmation password do not match.")]
        public string ConfirmPassword { get; set; }
    }
}
Modifying Register Post Method of Account Controller:

Next, modify the Register Post method of the Account Controller as follows. Here, we are adding the Phone Number property of the ApplicationUser class.

[HttpPost]
[AllowAnonymous]
public async Task<IActionResult> Register(RegisterViewModel model)
{
    if (ModelState.IsValid)
    {
        // Copy data from RegisterViewModel to ApplicationUser
        var user = new ApplicationUser
        {
            UserName = model.Email,
            Email = model.Email,
            FirstName = model.FirstName,
            LastName = model.LastName,
            PhoneNumber = model.PhoneNumber
        };

        // Store user data in AspNetUsers database table
        var result = await userManager.CreateAsync(user, model.Password);

        // If user is successfully created, sign-in the user using
        // SignInManager and redirect to index action of HomeController
        if (result.Succeeded)
        {
            //Then send the Confirmation Email to the User
            await SendConfirmationEmail(model.Email, user);

            // If the user is signed in and in the Admin role, then it is
            // the Admin user that is creating a new user. 
            // So redirect the Admin user to ListUsers action of Administration Controller
            if (signInManager.IsSignedIn(User) && User.IsInRole("Admin"))
            {
                return RedirectToAction("ListUsers", "Administration");
            }

            //If it is not Admin user, then redirect the user to RegistrationSuccessful View
            return View("RegistrationSuccessful");
        }

        // If there are any errors, add them to the ModelState object
        // which will be displayed by the validation summary tag helper
        foreach (var error in result.Errors)
        {
            ModelState.AddModelError(string.Empty, error.Description);
        }
    }

    return View(model);
}
Modifying Register.cshtml view

Next, modify the Register.cshtml view as follows. Here, we are adding the Phone Number field.

@model RegisterViewModel

@{
    ViewBag.Title = "User Registration";
}

<h1>User Registration</h1>

<div class="row">
    <div class="col-md-12">
        <form method="post">
            <div asp-validation-summary="All" class="text-danger"></div>
            <div class="form-group">
                <label asp-for="FirstName"></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"></label>
                <input asp-for="LastName" class="form-control" />
            </div>

            <div class="form-group">
                <label asp-for="Email"></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="PhoneNumber"></label>
                <input asp-for="PhoneNumber" class="form-control" />
                <span asp-validation-for="PhoneNumber" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Password"></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"></label>
                <input asp-for="ConfirmPassword" class="form-control" />
                <span asp-validation-for="ConfirmPassword" class="text-danger"></span>
            </div>
            <button type="submit" class="btn btn-primary">Register</button>
        </form>
    </div>
</div>

With these changes, register a new user with a valid phone number, as shown in the image below.

how to add a Phone Number in ASP.NET Core Identity

If you verify the database, you will see that the Phone Number column is filled with the value and PhoneNumberConfirmed is 0, as shown in the image below.

how to add a Phone Number in ASP.NET Core Identity

Now, let us proceed and see how to verify the Phone Number using ASP.NET Core Identity.

How do you verify a phone number is an ASP.NET Core Identity?

When the user is logged in, we need to display a dropdown list with the options to set the password, update the profile, and Confirm the Mobile Number options, as shown in the image below.

How do you verify a phone number is an ASP.NET Core Identity?

Once the User clicks on the Confirm Phone Number option, it will open the following page. The user needs to add or update the Phone number and click on the Confirm Phone Number button, as shown in the image below.

How do you verify a phone number is an ASP.NET Core Identity?

Note: As we use a Twilio trial account to send SMS, the mobile number you entered here must be registered in your Twilio account. Otherwise, an SMS will not be sent.

Once you click on the Confirm Phone Number, it will send a verification code (consisting of 6 digits) to the above phone number, and then it will open the following page. Here, you need to provide the verification code and click the Verify Phone Number button, as shown in the image below.

how to add and verify a Phone Number in ASP.NET Core Identity by sending an SMS

Once you enter the verification token and click on the Verify Phone Number button, if the token is valid, then it will confirm the phone number and display the following message.

how to add and verify a Phone Number in ASP.NET Core Identity

If you verify the database, you will see the PhoneNumberConfirmed column value as 1 for the above phone number, as shown in the image below.

how to add and verify a Phone Number in ASP.NET Core Identity

Modify _Layout.cshtml file:

First, modify the _Layout.cshtml file as follows to create a section for managing the logged-in user account.

@using Microsoft.AspNetCore.Identity
@inject SignInManager<ApplicationUser> SignInManager

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>@ViewData["Title"] - ASPNETCoreIdentityDemo</title>
    <link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css" />
    <link rel="stylesheet" href="~/css/site.css" asp-append-version="true" />
    <link rel="stylesheet" href="~/ASPNETCoreIdentityDemo.styles.css" asp-append-version="true" />
</head>
<body>
    <header>
        <nav class="navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-white border-bottom box-shadow mb-3">
            <div class="container-fluid">
                <a class="navbar-brand" asp-area="" asp-controller="Home" asp-action="Index">Home</a>
                <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target=".navbar-collapse" aria-controls="navbarSupportedContent"
                        aria-expanded="false" aria-label="Toggle navigation">
                    <span class="navbar-toggler-icon"></span>
                </button>
                <div class="navbar-collapse collapse d-sm-inline-flex justify-content-between">
                    <ul class="navbar-nav flex-grow-1">

                        <li class="nav-item">
                            <a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Privacy">Privacy</a>
                        </li>

                        <li class="nav-item">
                            <a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="SecureMethod">Secure</a>
                        </li>

                        <li class="nav-item">
                            <a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="NonSecureMethod">Non Secure</a>
                        </li>

                        @if (SignInManager.IsSignedIn(User) && User.IsInRole("Admin"))
                        {
                            <li class="nav-item dropdown">
                                <a class="nav-link dropdown-toggle" href="#" id="navbarDropdownMenuLink" role="button" 
                                data-bs-toggle="dropdown" aria-expanded="false">
                                    Manage
                                </a>
                                <ul class="dropdown-menu" aria-labelledby="navbarDropdownMenuLink">
                                    <a class="dropdown-item" asp-controller="Administration"
                                       asp-action="ListUsers">Users</a>
                                    <a class="dropdown-item" asp-controller="Administration"
                                       asp-action="ListRoles">Roles</a>
                                    <a class="dropdown-item" asp-controller="Administration"
                                       asp-action="ListRoles">Confirm Mobile Number</a>
                                </ul>
                            </li>
                        }
                    </ul>
                    <ul class="navbar-nav ml-auto">
                        @*If the user is signed-in display Logout link*@
                        @if (SignInManager.IsSignedIn(User))
                        {
                            <li class="nav-item dropdown">
                                <a class="nav-link dropdown-toggle" href="#" id="navbarDropdownMyAccountLink" role="button"
                                   data-bs-toggle="dropdown" aria-expanded="false">
                                    My Account
                                </a>
                                <ul class="dropdown-menu" aria-labelledby="navbarDropdownMyAccountLink">
                                    <a class="dropdown-item" asp-controller="Account"
                                       asp-action="ChangePassword">Set Password</a>
                                    <a class="dropdown-item" asp-controller="Account"
                                       asp-action="UpdateProfile">Update Profile</a>
                                    <a class="dropdown-item" asp-controller="Account"
                                       asp-action="ConfirmPhoneNumber">Confirm Phone Number</a>
                                </ul>
                            </li>

                            <li class="nav-item">
                                <form method="post" asp-controller="account" asp-action="logout">
                                    <button type="submit" style="width:auto"
                                            class="nav-link btn btn-link py-0">
                                        Logout @User?.Identity?.Name
                                    </button>
                                </form>
                            </li>
                        }
                        else
                        {
                            <li class="nav-item">
                                <a class="nav-link text-dark" asp-controller="account" asp-action="register">
                                    Register
                                </a>
                            </li>
                            <li class="nav-item">
                                <a class="nav-link text-dark" asp-controller="account" asp-action="login">
                                    Login
                                </a>
                            </li>
                        }
                    </ul>
                </div>
            </div>
        </nav>
    </header>
    <div class="container">
        <main role="main" class="pb-3">
            @RenderBody()
        </main>
    </div>

    <footer class="border-top footer text-muted">
        <div class="container">
            &copy; 2023 - ASPNETCoreIdentityDemo - <a asp-area="" asp-controller="Home" asp-action="Privacy">Privacy</a>
        </div>
    </footer>
    <script src="~/lib/jquery/dist/jquery.min.js"></script>
    <script src="~/lib/jquery-validation/dist/jquery.validate.min.js"></script>
    <script src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js"></script>
    <script src="~/lib/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
    <script src="~/js/site.js" asp-append-version="true"></script>
    @await RenderSectionAsync("Scripts", required: false)

</body>
</html>
Creating ConfirmPhoneNumber View Model:

Create a class file named ConfirmPhoneNumberViewModel.cs, and copy and paste the following code. This is the model used to confirm a user’s phone number and has only one property called PhoneNumber.

using System.ComponentModel.DataAnnotations;

namespace ASPNETCoreIdentityDemo.Models
{
    public class ConfirmPhoneNumberViewModel
    {
        [Phone(ErrorMessage = "Please Enter a Valid Phone Number")]
        [Required(ErrorMessage ="Please Enter Phone Number")]
        [Display(Name ="Phone Number")]
        public string PhoneNumber { get; set; }
    }
}
Adding ConfirmPhoneNumber Get Action Method:

Next, add the following ConfirmPhoneNumber Get Action method within the Account Controller, which will display the Confirm Phone Number view to the user, using which the user will confirm his phone number.

[Authorize]
[HttpGet]
public async Task<IActionResult> ConfirmPhoneNumber()
{
    //If the User already provided the Mobile while registering, we need to show that mobile number,
    //else we need to display empty and allow the user to add or update the mobile number

    var user = await userManager.GetUserAsync(User);
    if (user == null)
    {
        return NotFound($"Unable to load user with ID '{userManager.GetUserId(User)}'.");
    }

    ConfirmPhoneNumberViewModel model = new ConfirmPhoneNumberViewModel()
    {
        PhoneNumber = user.PhoneNumber
    };

    return View(model);
}
Adding ConfirmPhoneNumber.cshtml view

Next, add the ConfirmPhoneNumber.cshtml view within the Views/Account directory and then copy and paste the following code. This view will allow the user to add or update the phone number and then confirm it.

@model ConfirmPhoneNumberViewModel
@{
    ViewData["Title"] = "ConfirmPhoneNumber";
}

<h1>Confirm Phone Number</h1>
<hr />
<div class="row">
    <div class="col-md-6">
        <form method="post" asp-action="SendPhoneVerificationCode" asp-controller="Account">
            <div asp-validation-summary="All" class="text-danger"></div>
            <div class="form-group">
                <label>Add or Update Phone Number</label>
                <input asp-for="PhoneNumber" class="form-control" />
                <span asp-validation-for="PhoneNumber" class="text-danger"></span>
            </div>
            <br />
            <button type="submit" class="btn btn-primary">Confirm Phone Number</button>
        </form>
    </div>
</div>
Injecting the SMS Sender Service:

In our previous article, we created the SMSSender service to send SMS. So, we need to inject the SMS Sender Service within the Account Controller as follows. We will verify the phone number by sending a confirmation token using this SMS Sender Service.

Injecting the SMS Sender Service

SendPhoneVerificationCode Post Action Method

Next, add the following SendPhoneVerificationCode Post action method within the Account Controller. This method sends the verification code to the registered phone number. The following code is self-explained, so please go through the comment lines for a better understanding.

[Authorize]
[HttpPost]
public async Task<IActionResult> SendPhoneVerificationCode(ConfirmPhoneNumberViewModel model)
{
    if (ModelState.IsValid)
    {
        var user = await userManager.GetUserAsync(User);
        if (user == null)
        {
            return NotFound($"Unable to load user with ID '{userManager.GetUserId(User)}'.");
        }

        //Generate the Token
        var token = await userManager.GenerateChangePhoneNumberTokenAsync(user, model.PhoneNumber);

        // Code to send the token via SMS 
        var result = await smsSender.SendSmsAsync(model.PhoneNumber, token);

        if (result)
        {
            // Save or pass the phone number for later verification
            TempData["PhoneNumber"] = model.PhoneNumber;

            // Redirect to verification view
            return RedirectToAction("VerifyPhoneNumber", "Account"});
        }
        else
        {
            ViewBag.ErrorTitle = "Unable to send SMS";
            ViewBag.ErrorMessage = "Please try after some time";
            return RedirectToAction("Error");
        }
    }

    return View(model);
}

GenerateChangePhoneNumberTokenAsync: The GenerateChangePhoneNumberTokenAsync method in ASP.NET Core Identity generates a token for phone number verification. This token is typically sent to the user’s phone number via SMS and is used to confirm that the user owns and has access to that phone number.

VerifyPhoneNumber Get Action Method.

Once we send the verification token to the user’s phone number, we need a view where the user will enter the verification token to confirm the phone number. So, add the following VerifyPhoneNumber Get Action method within the Account Controller. This action method renders a view where the user will enter the verification code.

[Authorize]
[HttpGet]
public IActionResult VerifyPhoneNumber()
{
    TempData["PhoneNumber"] = TempData["PhoneNumber"] as string;
    return View();
}
VerifyPhoneNumber View

Next, add the following VerifyPhoneNumber.cshtml view within the Views/Account folder and then copy and paste the following code. This will render the view where the user will enter the verification token received in his/her mobile number.

@{
    ViewData["Title"] = "VerifyPhoneNumber";
}

<h1>Verify PhoneNumber</h1>

<hr />
<div class="row">
    <div class="col-md-6">
        <form method="post" asp-action="VerifyPhoneNumber" asp-controller="Account">
            <div class="form-group">
                <label>Enter the Verification Token Send to Your Phone Number</label>
                <input type="text" name="Token" id="Token" class="form-control" />
            </div>

            <button type="submit" class="btn btn-primary">Verify Phone Number</button>
        </form>
    </div>
</div>
VerifyPhoneNumber Post Action Method

Once the user enters the verification token and clicks the submit button, we need a post-action method to verify the token and update the data in the database. S0, add the following VerifyPhoneNumber Post Action method within the Account Controller.

[Authorize]
[HttpPost]
public async Task<IActionResult> VerifyPhoneNumber(string Token)
{
    var PhoneNumber = TempData["PhoneNumber"] as string;
    var user = await userManager.GetUserAsync(User);

    var result = await userManager.VerifyChangePhoneNumberTokenAsync(user, Token, PhoneNumber);

    if (result)
    {
        // Update user's PhoneNumber and PhoneNumberConfirmed
        user.PhoneNumber = PhoneNumber;
        user.PhoneNumberConfirmed = true;
        await userManager.UpdateAsync(user);

        // Redirect to success page or show success message
        return View("PhoneVerificationSuccessful");
    }
    else
    {
        // Handle verification failure
        ViewBag.ErrorTitle = "Verification Failed";
        ViewBag.ErrorMessage = "Either the Token Expired or you entered an invalid token";
        return RedirectToAction("Error");
    }
}
PhoneVerificationSuccessful Action Method

Next, add the following action method within the Account Controller. This action method will execute when the user successfully verifies the phone number.

[Authorize]
[HttpGet]
public IActionResult PhoneVerificationSuccessful()
{
    return View();
}

Next, add the following PhoneVerificationSuccessful.cshtml view within the Views/Account folder and then copy and paste the following code.

@{
    ViewData["Title"] = "PhoneVerificationSuccessful";
}

<h1>Phone Verification Successful</h1>

Now, test the application, and it should work as expected.

In the next article, I will discuss How to Implement 2 Factor Authentication in ASP.NET Core Identity. In this article, I explain How to Add and Verify a Phone Number in ASP.NET Core Identity. I hope you enjoy this How to Add and Verify Phone Number in ASP.NET Core Identity article.

Leave a Reply

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