dotnet ef database update
update-database
HttpClient
. Luckily the SDK has been updated to work with version 2 of the Verify API, which drastically simplifies interacting with the API. Version 2 of the API also allows working with E.164 formatted numbers.<ItemGroup>
section of the project file should look like this (version numbers may be higher):<ItemGroup> <PackageReference Include="Microsoft.AspNetCore.App"/> <PackageReference Include="Microsoft.AspNetCore.Razor.Design" Version="2.2.0" PrivateAssets="All" /> <PackageReference Include="Twilio" Version="5.29.1" /> </ItemGroup>
{ "Twilio": { "AccountSID": "ACxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", "AuthToken": "your_auth_token", "VerificationServiceSID": "VAxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" } }
using Twilio;
at the top of Startup.cs, and add the following at the end of ConfigureServices
:public void ConfigureServices(IServiceCollection services) { // existing configuration var accountSid = Configuration["Twilio:AccountSID"]; var authToken = Configuration["Twilio:AuthToken"]; TwilioClient.Init(accountSid, authToken); }
HttpClientFactory
features introduced in ASP.NET Core 2.1, see a previous post on the Twilio blog for an alternative approach.public class TwilioVerifySettings { public string VerificationServiceSID { get; set; } }
Startup.ConfigureServices
method:services.Configure<TwilioVerifySettings>(Configuration.GetSection("Twilio"));
@using Microsoft.AspNetCore.Identity @using SendVerificationSmsV2Demo.Areas.Identity @namespace SendVerificationSmsV2Demo.Areas.Identity.Pages @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
PhoneNumber
property for an IdentityUser
is the E.164 formatted phone number.VerifyPhoneModel
class in the code-behind file VerifyPhone.cshtml.cs with the following:using System; using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.RazorPages; using Microsoft.Extensions.Options; using Twilio.Rest.Verify.V2.Service; namespace SendVerificationSmsV2Demo.Areas.Identity.Pages.Account { [Authorize] public class VerifyPhoneModel : PageModel { private readonly TwilioVerifySettings _settings; private readonly UserManager<IdentityUser> _userManager; public VerifyPhoneModel(IOptions<TwilioVerifySettings> settings, UserManager<IdentityUser> userManager) { _settings = settings.Value; _userManager = userManager; } public string PhoneNumber { get; set; } public async Task<IActionResult> OnGetAsync() { await LoadPhoneNumber(); return Page(); } public async Task<IActionResult> OnPostAsync() { await LoadPhoneNumber(); try { var verification = await VerificationResource.CreateAsync( to: PhoneNumber, channel: "sms", pathServiceSid: _settings.VerificationServiceSID ); if (verification.Status == "pending") { return RedirectToPage("ConfirmPhone"); } ModelState.AddModelError("", $"There was an error sending the verification code: {verification.Status}"); } catch (Exception) { ModelState.AddModelError("", "There was an error sending the verification code, please check the phone number is correct and try again"); } return Page(); } private async Task LoadPhoneNumber() { var user = await _userManager.GetUserAsync(User); if (user == null) { throw new Exception($"Unable to load user with ID '{_userManager.GetUserId(User)}'."); } PhoneNumber = user.PhoneNumber; } } }
OnGetAsync
using the LoadPhoneNumber
helper method, and is assigned to the PhoneNumber
property for display in the UI. The OnPostAsync
handler is where the verification process begins.OnPostAsync
method and used to send a verification message with the Twilio helper SDK. The VerificationResource.CreateAsync
method sends a verification code to the provided number using the Twilio Verify API. When calling this method you also need to provide the Verify Service ID. You retrieve the value from configuration by injecting an IOptions<TwilioVerifySettings>
into the page constructor using the Options pattern.Status
field indicating the overall status of the verification process. If the message is sent successfully, the response will return "pending"
, indicating that a check is waiting to be performed. On success, the user is redirected to the ConfirmPhone
page, which you'll create shortly. If the Verify API indicates the request failed, or if an exception is thrown, an error is added to the ModelState
, and the page is re-displayed to the user.@page @model VerifyPhoneModel @{ ViewData["Title"] = "Verify Phone number"; } <h4>@ViewData["Title"]</h4> <div class="row"> <div class="col-md-8"> <form method="post"> <p> We will verify your phone number by sending a code to @Model.PhoneNumber. </p> <div asp-validation-summary="All" class="text-danger"></div> <button type="submit" class="btn btn-primary">Send verification code</button> </form> </div> </div>
Your Twilio Verify API demo verification code is: 293312
ConfirmPhoneModel
class with the following code:using System; using System.ComponentModel.DataAnnotations; using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.RazorPages; using Microsoft.Extensions.Options; using Twilio.Rest.Verify.V2.Service; namespace SendVerificationSmsV2Demo.Areas.Identity.Pages.Account { [Authorize] public class ConfirmPhoneModel : PageModel { private readonly TwilioVerifySettings _settings; private readonly UserManager<IdentityUser> _userManager; public ConfirmPhoneModel(UserManager<IdentityUser> userManager, IOptions<TwilioVerifySettings> settings) { _userManager = userManager; _settings = settings.Value; } public string PhoneNumber { get; set; } [BindProperty, Required, Display(Name = "Code")] public string VerificationCode { get; set; } public async Task<IActionResult> OnGetAsync() { await LoadPhoneNumber(); return Page(); } public async Task<IActionResult> OnPostAsync() { await LoadPhoneNumber(); if (!ModelState.IsValid) { return Page(); } try { var verification = await VerificationCheckResource.CreateAsync( to: PhoneNumber, code: VerificationCode, pathServiceSid: _settings.VerificationServiceSID ); if (verification.Status == "approved") { var identityUser = await _userManager.GetUserAsync(User); identityUser.PhoneNumberConfirmed = true; var updateResult = await _userManager.UpdateAsync(identityUser); if (updateResult.Succeeded) { return RedirectToPage("ConfirmPhoneSuccess"); } else { ModelState.AddModelError("", "There was an error confirming the verification code, please try again"); } } else { ModelState.AddModelError("", $"There was an error confirming the verification code: {verification.Status}"); } } catch (Exception) { ModelState.AddModelError("", "There was an error confirming the code, please check the verification code is correct and try again"); } return Page(); } private async Task LoadPhoneNumber() { var user = await _userManager.GetUserAsync(User); if (user == null) { throw new Exception($"Unable to load user with ID '{_userManager.GetUserId(User)}'."); } PhoneNumber = user.PhoneNumber; } } }
OnGetAsync
handler loads the current user's phone number for display in the UI using the LoadPhoneNumber
helper method. The phone number is loaded again in the OnPostAsync
handler for calling the verification check API. You verify the user's code by calling VerificationCheckResource.CreateAsync
, passing in the phone number, the provided verification code, and the Verify Service ID.result.Status="approved"
. You can store the confirmation result on the IdentityUser
object directly by setting the PhoneNumberConfirmed
property and saving the changes.ConfirmPhoneSuccess
page (that you'll create shortly). If there are any errors or exceptions, an error is added to the ModelState
and the page is redisplayed.@page @model ConfirmPhoneModel @{ ViewData["Title"] = "Confirm Phone number"; } <h4>@ViewData["Title"]</h4> <div class="row"> <div class="col-md-6"> <form method="post"> <p> We have sent a confirmation code to @Model.PhoneNumber Enter the code you receive to confirm your phone number. </p> <div asp-validation-summary="All" class="text-danger"></div> <div class="form-group"> <label asp-for="VerificationCode"></label> <input asp-for="VerificationCode" class="form-control" type="number" /> <span asp-validation-for="VerificationCode" class="text-danger"></span> </div> <button type="submit" class="btn btn-primary">Confirm</button> </form> </div> </div> @section Scripts { <partial name="_ValidationScriptsPartial" /> }
@page @model ConfirmPhoneSuccessModel @{ ViewData["Title"] = "Phone number confirmed"; } <h1>@ViewData["Title"]</h1> <div> <p> Thank you for confirming your phone number. </p> <a asp-page="/Index">Back to home</a> </div>
VerifyPhone
page. Currently you have to navigate manually to /Identity/Account/VerifyPhone, but in practice you would want to add a link to it somewhere in your app.IdentityUser.PhoneNumberConfirmed
property anywhere in the app.PhoneNumberConfirmed=true
in the VerifyPhone
page, as well as hide any verification links.ConfirmPhone
page.