Back to: ASP.NET Core Identity Tutorials
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:
Why Do We Need to Verify Phone Number in ASP.NET Core Identity?
Verifying phone numbers in ASP.NET Core Identity is a crucial step for enhancing web applications’ security, reliability, and user experience. Phone number verification serves multiple purposes that collectively safeguard user accounts and ensure effective communication between the application and its users. The following are the key reasons:
Two-Factor Authentication (2FA):
One of the primary reasons for phone number verification is to enable two-factor authentication. By requiring users to provide a second form of verification, such as a code sent via SMS or a call to complement their password, you significantly reduce the risk of unauthorized access. Even if a malicious attacker obtains a user’s password without access to the verified phone number, they cannot complete the login process.
Protection Against Account Takeover:
Phone number verification helps prevent account takeovers. If a user’s account is compromised, the attacker would need access to the verified phone number to perform sensitive actions like password resets or changes to account settings, adding an extra layer of defense.
Account Recovery:
In scenarios where users forget their passwords or lose access to their accounts, a verified phone number provides a reliable method for account recovery. By sending a recovery code via SMS or voice call, users can securely reset their passwords and regain access without relying solely on email, which might be less secure or accessible.
User Identity Verification
Verifying phone numbers helps ensure that each user account is tied to a unique and real individual. This reduces the likelihood of fraudulent accounts, spam registrations, and abusive behavior within the application. It also builds trust among users, knowing that the platform takes steps to verify identities.
Improved Communication
A verified phone number serves as a direct and immediate communication channel between the application and the user. This can send important alerts, updates, or reminders directly to the user’s phone. It can also communicate promotions, offers, or personalized content.
Implementation of Phone Number Verification in ASP.NET Core Identity
Phone number verification can be implemented using SMS providers like Twilio, Nexmo, or any custom SMS gateway. ASP.NET Core Identity provides a built-in mechanism for verifying phone numbers, such as:
- Generating and sending an OTP (One-Time Password) to the user’s phone.
- Allowing the user to confirm the OTP via a form.
- Marking the phone number as verified if the OTP matches.
Let us proceed with implementing phone verification in our ASP.NET Core Application step by step.
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. So, please modify the RegisterViewModel as follows.
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; } [Required] [Phone] [Display(Name = "Phone Number")] public string? PhoneNumber { get; set; } [Required] [DataType(DataType.Password)] public string Password { get; set; } [Required] [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 action method of the Account Controller as follows. Now, we will also get the Phone number as part of the Registration process, and then we need to set 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 IdentityUser var user = new ApplicationUser { FirstName = model.FirstName, LastName = model.LastName, UserName = model.Email, Email = model.Email, PhoneNumber = model.PhoneNumber //Added to Store the Phone Number }; // 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 (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 input field to take the phone number from the user.
@model RegisterViewModel @{ ViewBag.Title = "Register Your Account"; } <div class="container mt-5"> <div class="row justify-content-center"> <div class="col-md-8"> <div class="card shadow-sm"> <div class="card-header text-center bg-primary text-white"> <h3>Create Your Account</h3> </div> <div class="card-body"> <p class="text-center text-muted"> Please fill out the form below to create a new account. Fields marked with an asterisk (*) are required. </p> <form method="post" asp-controller="Account" asp-action="Register"> <!-- Validation Summary --> @if (!ViewData.ModelState.IsValid) { <div asp-validation-summary="All" class="alert alert-danger" role="alert"></div> } <!-- First Name --> <div class="row mb-3 align-items-center"> <div class="col-md-3 text-end"> <label asp-for="FirstName" class="form-label"> First Name <span class="text-danger">*</span> </label> </div> <div class="col-md-9"> <input asp-for="FirstName" class="form-control" placeholder="Enter your first name" /> <span asp-validation-for="FirstName" class="text-danger"></span> </div> </div> <!-- Last Name --> <div class="row mb-3 align-items-center"> <div class="col-md-3 text-end"> <label asp-for="LastName" class="form-label"></label> </div> <div class="col-md-9"> <input asp-for="LastName" class="form-control" placeholder="Enter your last name" /> <span asp-validation-for="LastName" class="text-danger"></span> </div> </div> <!-- Email --> <div class="row mb-3 align-items-center"> <div class="col-md-3 text-end"> <label asp-for="Email" class="form-label"> Email <span class="text-danger">*</span> </label> </div> <div class="col-md-9"> <input asp-for="Email" class="form-control" placeholder="Enter your email address" /> <span asp-validation-for="Email" class="text-danger"></span> </div> </div> <!-- Phone Number --> <div class="row mb-3 align-items-center"> <div class="col-md-3 text-end"> <label asp-for="PhoneNumber" class="form-label"> PhoneNumber <span class="text-danger">*</span> </label> </div> <div class="col-md-9"> <input asp-for="PhoneNumber" class="form-control" placeholder="Enter your phone number" /> <span asp-validation-for="PhoneNumber" class="text-danger"></span> </div> </div> <!-- Password --> <div class="row mb-3 align-items-center"> <div class="col-md-3 text-end"> <label asp-for="Password" class="form-label"> Password <span class="text-danger">*</span> </label> </div> <div class="col-md-9"> <input asp-for="Password" type="password" class="form-control" placeholder="Create a password" /> <span asp-validation-for="Password" class="text-danger"></span> </div> </div> <!-- Confirm Password --> <div class="row mb-3 align-items-center"> <div class="col-md-3 text-end"> <label asp-for="ConfirmPassword" class="form-label"> Confirm Password <span class="text-danger">*</span> </label> </div> <div class="col-md-9"> <input asp-for="ConfirmPassword" type="password" class="form-control" placeholder="Confirm your password" /> <span asp-validation-for="ConfirmPassword" class="text-danger"></span> </div> </div> <!-- Submit Button --> <div class="d-grid"> <button type="submit" class="btn btn-primary btn-block"> Register </button> </div> </form> </div> </div> <div class="text-center mt-3"> <p class="text-muted"> Already have an account? <a asp-action="Login" asp-controller="Account" class="text-primary">Login here</a>. </p> </div> </div> </div> </div>
Register a New User:
Now, with these changes, register a new user with a valid phone number, as shown in the below image.
Verify Your Email:
Now, you will get your Email Confirmation Mail to the provided Email ID, and to log in, you have to confirm your identity by clicking on the link. Please verify your Email ID by clicking the Confirm Email Link you received in your Email.
Verify the Phone Number:
Now, if you verify the database, then you will see that the Phone Number column is filled with the value and the PhoneNumberConfirmed column is set to 0, as shown in the below image.
Now, let us proceed and see how to verify the Phone Number using ASP.NET Core Identity.
How Do We Verify Phone Number in ASP.NET Core Identity?
When the user is logged in, we need to display the Confirm Mobile Number option within the My Account dropdown list, as shown in the image below.
Once the User clicks on the Verify Phone Number button, it will open the following page. Here, the user needs to add or update the phone number and click on the Confirm Phone Number button, as shown in the image below.
Note: As we are using Twilio trial account to send SMS, so, the mobile number you entered here must be registered in your Twilio account, otherwise SMS will not be sent.
Once you click on the Confirm Phone Number button, 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 on the Verify Phone Number button, as shown in the below image.
If you click on the Resend Code button, you will get the below message:
Once you enter the verification token and click on the Verify Phone Number button, if the token is valid, it will confirm the phone number and display the following message.
Now, if you verify the database, then you will see the PhoneNumberConfirmed column value as 1 for the above phone number, as shown in the image below.
Modify _Layout.cshtml file:
First, modify the _Layout.cshtml file as follows to include the Confirm Phone Number link within the My Account dropdown list.
@using Microsoft.AspNetCore.Identity @inject SignInManager<ApplicationUser> SignInManager @inject UserManager<ApplicationUser> UserManager <!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" /> </head> <body class="d-flex flex-column min-vh-100"> <header> <nav class="navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-light shadow-sm"> <div class="container-fluid"> <a class="navbar-brand" asp-area="" asp-controller="Home" asp-action="Index"> <img src="~/images/logo.png" alt="Logo" style="height:40px;"> </a> <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation"> <span class="navbar-toggler-icon"></span> </button> <div class="collapse navbar-collapse" id="navbarNav"> <ul class="navbar-nav me-auto"> <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") || User.IsInRole("SuperAdmin"))) { <li class="nav-item dropdown"> <a class="nav-link dropdown-toggle" href="#" id="manageDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false"> Manage </a> <ul class="dropdown-menu" aria-labelledby="manageDropdown"> <li><a class="dropdown-item" asp-controller="Administration" asp-action="ListUsers">Users</a></li> <li><a class="dropdown-item" asp-controller="Administration" asp-action="ListRoles">Roles</a></li> </ul> </li> } </ul> <ul class="navbar-nav"> @if (SignInManager.IsSignedIn(User)) { <!-- Display username --> <li class="nav-item"> <span class="nav-link">Hello, @User.Identity.Name!</span> </li> <li class="nav-item dropdown"> <a class="nav-link dropdown-toggle" href="#" id="accountDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false"> My Account </a> <ul class="dropdown-menu dropdown-menu-end" aria-labelledby="accountDropdown"> @{ // Fetch the current user var currentUser = await UserManager.GetUserAsync(User); // Check if the user has a password bool hasPassword = await UserManager.HasPasswordAsync(currentUser); if (!hasPassword) { // Show "Set Password" link if no local password is set <li> <a class="dropdown-item" asp-controller="Account" asp-action="SetPassword">Set Password</a> </li> } else { // Show "Change Password" link if a local password is set <li> <a class="dropdown-item" asp-controller="Account" asp-action="ChangePassword">Change Password</a> </li> } } <li> <a class="dropdown-item" asp-controller="Account" asp-action="ConfirmPhoneNumber">Verify Phone Number</a> </li> <li> <form method="post" asp-controller="Account" asp-action="Logout" style="display:inline;"> <button type="submit" class="dropdown-item">Sign Out</button> </form> </li> </ul> </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 flex-grow-1"> <main role="main" class="pb-3"> @RenderBody() </main> </div> <footer class="bg-light text-center text-lg-start border-top py-3 mt-auto"> <div class="container"> <p class="mb-0 text-muted"> © 2024 ASPNETCoreIdentityDemo - <a asp-area="" asp-controller="Home" asp-action="Privacy" class="text-decoration-none">Privacy</a> </p> </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/dist/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 Confirm Phone Number View Model:
Create a class file named ConfirmPhoneNumberViewModel.cs within the Models folder, and then copy and paste the following code. This model takes the phone number of a user 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; } } }
Creating Verify Phone Number View Model
Create a class file named VerifyPhoneNumberViewModel.cs within the Models folder, and then copy and paste the following code. This model is used to verify the phone number of a user and has only one property called Token.
using System.ComponentModel.DataAnnotations; namespace ASPNETCoreIdentityDemo.Models { public class VerifyPhoneNumberViewModel { [Required(ErrorMessage = "Verification code is required.")] [StringLength(6, MinimumLength = 6, ErrorMessage = "Verification code must be 6 digits.")] [RegularExpression(@"^\d{6}$", ErrorMessage = "Verification code must be numeric.")] [Display(Name = "Verification Code")] public string Token { get; set; } } }
Adding Confirm Phone Number 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 the user to 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 Confirm Phone Number View
Next, add the ConfirmPhoneNumber.cshtml view within the Views/Account directory and copy and paste the following code. This view will allow the user to add or update the phone number and then confirm the phone number.
@model ConfirmPhoneNumberViewModel @{ ViewData["Title"] = "Confirm Phone Number"; } <div class="container mt-5"> <div class="row justify-content-center"> <div class="col-md-6"> <div class="card shadow-sm"> <div class="card-header text-center bg-primary text-white"> <h3>Phone Number Confirmation</h3> </div> <div class="card-body"> <p class="text-muted text-center"> Please add or update your phone number below. We will send a verification code to your phone to confirm ownership. </p> <form method="post" asp-action="SendPhoneVerificationCode" asp-controller="Account"> <!-- Validation Summary --> @if (!ViewData.ModelState.IsValid) { <div asp-validation-summary="All" class="alert alert-danger" role="alert"></div> } <!-- Phone Number --> <div class="row mb-3 align-items-center"> <div class="col-md-4 text-end"> <label asp-for="PhoneNumber" class="form-label">Phone Number</label> </div> <div class="col-md-8"> <input asp-for="PhoneNumber" class="form-control" placeholder="Enter your phone number" /> <span asp-validation-for="PhoneNumber" class="text-danger"></span> </div> </div> <!-- Submit Button --> <div class="d-grid"> <button type="submit" class="btn btn-primary"> <i class="bi bi-check-circle-fill"></i> Confirm Phone Number </button> </div> </form> </div> </div> <div class="text-center mt-3"> <p class="text-muted"> Need help? <a href="/Help" class="text-primary">Contact Support</a>. </p> </div> </div> </div> </div>
Injecting the SMS Sender Service into the Account Controller:
Next, we need to inject the SMS Sender Service into the Account Controller as follows. Using this SMS Sender Service, we will verify the phone number by sending a confirmation token.
public class AccountController : Controller { //userManager will hold the UserManager instance private readonly UserManager<ApplicationUser> userManager; //signInManager will hold the SignInManager instance private readonly SignInManager<ApplicationUser> signInManager; //EmailSenderService will hold the EmailSenderService instance private readonly EmailSenderService emailSender; //SMSSender will hold the SMSSender instance private readonly SMSSender smsSender; // UserManager, SignInManager, EmailSenderService and SMSSender services are injected into the AccountController // using constructor injection public AccountController(UserManager<ApplicationUser> userManager, SignInManager<ApplicationUser> signInManager, EmailSenderService emailSender, SMSSender smsSender) { this.userManager = userManager; this.signInManager = signInManager; this.emailSender = emailSender; this.smsSender = smsSender; } //Rest of all code }
Send Phone Verification Code 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 read the comment lines for a better understanding.
[Authorize] [HttpPost] public async Task<IActionResult> SendPhoneVerificationCode(ConfirmPhoneNumberViewModel model) { if (ModelState.IsValid && !string.IsNullOrEmpty(model.PhoneNumber)) { // Retrieve the logged-in user var user = await userManager.GetUserAsync(User); if (user == null) { return RedirectToAction("Login", "Account"); } // Generate a verification token for the provided phone number var token = await userManager.GenerateChangePhoneNumberTokenAsync(user, model.PhoneNumber); // Send the token via SMS var result = await smsSender.SendSmsAsync(model.PhoneNumber, $"Your verification code is: {token}. Please enter this code to confirm your phone number."); if (result) { // Store the phone number temporarily for verification purposes TempData["PhoneNumber"] = model.PhoneNumber; // Redirect the user to the VerifyPhoneNumber page TempData["SuccessMessage"] = "A verification code has been sent to your phone number. Please enter the code to complete the verification."; return RedirectToAction("VerifyPhoneNumber", "Account"); } else { // Log and display an error message if SMS sending fails TempData["ErrorMessage"] = "We encountered an issue while sending the verification code. Please try again later."; return RedirectToAction("Error"); } } // If the model state is invalid, reload the current view with validation errors TempData["ErrorMessage"] = "There were errors in your submission. Please correct them and try again."; return View(model); }
Note: 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.
Verify Phone Number 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(); }
Verify Phone Number View
Next, add the following VerifyPhoneNumber.cshtml view within the Views/Account folder and 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.
@model VerifyPhoneNumberViewModel @{ ViewData["Title"] = "Verify Phone Number"; } <div class="container mt-5"> <div class="row justify-content-center"> <div class="col-md-6"> <div class="card shadow-sm"> <div class="card-header text-center bg-primary text-white"> <h3>Phone Number Verification</h3> </div> <div class="card-body"> <!-- Success Message --> @if (TempData["SuccessMessage"] != null) { <div class="alert alert-success"> @TempData["SuccessMessage"] </div> } <!-- Error Message --> @if (TempData["ErrorMessage"] != null) { <div class="alert alert-danger"> @TempData["ErrorMessage"] </div> } <!-- Informative Introductory Message --> @if (TempData["SuccessMessage"] == null && TempData["ErrorMessage"] == null) { <p class="text-muted text-center"> A verification code has been sent to your phone number. Please enter the code below to confirm your phone number. </p> } <form method="post" asp-action="VerifyPhoneNumber" asp-controller="Account"> <!-- Token Input --> <div class="row mb-3 align-items-center"> <div class="col-md-4 text-end"> <label for="Token" class="form-label">Verification Code</label> </div> <div class="col-md-8"> <input asp-for="Token" class="form-control" placeholder="Enter verification code" /> <span asp-validation-for="Token" class="text-danger"></span> </div> </div> <!-- Submit Button --> <div class="d-grid"> <button type="submit" class="btn btn-primary"> Verify Phone Number </button> </div> </form> </div> </div> <div class="text-center mt-3"> <form method="post" asp-action="ResendPhoneVerificationCode" asp-controller="Account"> <p class="text-muted"> Didn't receive the code? <button type="submit" class="btn btn-link p-0 m-0 align-baseline">Resend Code</button> </p> </form> </div> </div> </div> </div>
Resend Phone Verification Code Post Action Method
Next, add the following ResendPhoneVerificationCode Post action method within the Account Controller. This method resends the verification code to the registered phone number. The following code is self-explained, so please read the comment lines for a better understanding.
[Authorize] [HttpPost] public async Task<IActionResult> ResendPhoneVerificationCode() { // Get the currently logged-in user var user = await userManager.GetUserAsync(User); if (user == null) { TempData["ErrorMessage"] = "Unable to load user information. Please log in again."; return RedirectToAction("Login", "Account"); } //Get the Phone number from TempData var PhoneNumber = TempData["PhoneNumber"] as string; // Check if the user has a phone number to resend the token if (string.IsNullOrEmpty(user.PhoneNumber)) { TempData["ErrorMessage"] = "Your phone number is not set. Please update your phone number to continue."; return RedirectToAction("ConfirmPhoneNumber", "Account"); } // Generate a new verification token var token = await userManager.GenerateChangePhoneNumberTokenAsync(user, user.PhoneNumber); try { // Send the token via SMS var result = await smsSender.SendSmsAsync(PhoneNumber, $"Your new verification code is: {token}. Please enter this code to verify your phone number."); // Store the phone number temporarily for verification purposes TempData["PhoneNumber"] = PhoneNumber; if (result) { TempData["SuccessMessage"] = "A new verification code has been sent to your phone. Please enter the code below to confirm your phone number."; } else { TempData["ErrorMessage"] = "We were unable to resend the verification code. Please try again later."; return View("Error"); } } catch (Exception ex) { // Log the exception and provide user feedback TempData["ErrorMessage"] = $"An error occurred while sending the verification code: {ex.Message}"; return View("Error"); } // Redirect back to the verification page return RedirectToAction("VerifyPhoneNumber", "Account"); }
Verify Phone Number 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. So, add the following VerifyPhoneNumber Post Action method within the Account Controller.
[Authorize] [HttpPost] public async Task<IActionResult> VerifyPhoneNumber(VerifyPhoneNumberViewModel model) { if (!ModelState.IsValid) { // Model validation failed; redisplay the form with validation errors return View(model); } var phoneNumber = TempData["PhoneNumber"] as string; var user = await userManager.GetUserAsync(User); if (user == null || string.IsNullOrEmpty(phoneNumber)) { TempData["ErrorMessage"] = "Unable to load user information. Please log in again."; return RedirectToAction("Login", "Account"); } var isTokenValid = await userManager.VerifyChangePhoneNumberTokenAsync(user, model.Token, phoneNumber); if (isTokenValid) { // Update user's PhoneNumber and PhoneNumberConfirmed user.PhoneNumber = phoneNumber; user.PhoneNumberConfirmed = true; var updateResult = await userManager.UpdateAsync(user); if (updateResult.Succeeded) { return RedirectToAction("PhoneVerificationSuccessful", "Account"); } else { // Handle update failures TempData["ErrorMessage"] = "Either the token has expired or you entered an invalid token."; return View(model); } } else { // Handle verification failure TempData["ErrorMessage"] = "Either the token has expired or you entered an invalid token."; // Preserve PhoneNumber in TempData for potential further actions TempData["PhoneNumber"] = phoneNumber; return View(model); } }
Phone Verification Successful 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(); }
Phone Verification Successful View
Next, add the following PhoneVerificationSuccessful.cshtml view within the Views/Account folder and copy and paste the following code.
@{ ViewData["Title"] = "Phone Verification Successful"; } <div class="container mt-5"> <div class="row justify-content-center"> <div class="col-md-6"> <div class="card shadow-sm"> <div class="card-header text-center bg-success text-white"> <h3>Verification Successful</h3> </div> <div class="card-body text-center"> <p class="text-success fs-4"> <i class="bi bi-check-circle-fill"></i> Congratulations! Your phone number has been successfully verified. </p> <p class="text-muted"> You can now enjoy the full features of your account. Thank you for verifying your phone number. </p> <div class="d-grid mt-4"> <a href="/" class="btn btn-primary"> Go to Home </a> </div> </div> </div> </div> </div> </div>
Now, test the application, and it should work as expected.
Token Storage and Verification Process with ASP.NET Core Identity:
In ASP.NET Core Identity, the process of phone number verification involves generating and verifying a token. This token serves as a One-Time Password (OTP) sent via SMS to the user’s phone number, which the user must enter to confirm ownership of that phone number. The following is how the token and its verification process work behind the scenes in ASP.NET Core Identity:
- Token Generation: When the user provides their phone number, ASP.NET Core Identity uses the UserManager<TUser> service to generate a token. This is done through the method GenerateChangePhoneNumberTokenAsync, which creates a security token that can be sent to the phone number via SMS.
- Token Sending: The generated token is sent to the user’s phone number using an SMS provider (like Twilio or Nexmo). This part is typically handled by a custom SMS sender service integrated into your application.
- Token Storage: The generated token is not stored in the database. Instead, it’s temporarily stored on the server, usually in memory, and it is associated with the user. It has a limited validity period, after which it expires.
- Token Verification: When the user enters the received token on your website to confirm their phone number, the VerifyChangePhoneNumberTokenAsync method of UserManager<TUser> is called. This method checks if the token matches the one generated for the user’s phone number and if it is still valid.
- Database Update: If the token is valid, the user’s phone number is marked as confirmed in the database (PhoneNumberConfirmed = true). This field is part of the user’s record in the AspNetUsers table, ensuring that the phone number has been verified.
Verifying a phone number in ASP.NET Core Identity ensures the application’s security, reduces fraud, enhances communication, and complies with regulations. It’s a critical feature for modern applications to maintain user trust and provide a seamless, secure experience.
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.