Verificación de la propiedad del número de teléfono con Twilio usando ASP.NET Core Identity y Razor Pages

July 21, 2021
Redactado por

Verificación de la propiedad del número de teléfono con Twilio usando ASP.NET Core Identity y Razor Pages

ASP.NET Core Identity es un sistema de membresía que agrega funcionalidad de inicio de sesión de usuarios y administración de usuarios a las aplicaciones ASP.NET Core. Incluye muchas funciones creativas y tiene soporte básico para almacenar un número de teléfono para un usuario. De manera predeterminada, ASP.NET Core Identity no intenta verificar la propiedad de los números de teléfono, pero usted puede agregar esa funcionalidad usted mismo mediante la integración de las funciones de verificación de identidad de Twilio en su aplicación.

En esta publicación, aprenderá cómo puede probar la propiedad de un número de teléfono proporcionado por un usuario mediante Twilio Verify en una aplicación ASP.NET Core mediante Razor Pages. Esto implica enviar un código en un mensaje SMS al número de teléfono proporcionado. El usuario ingresa el código recibido y Twilio confirma si es correcto. Si es así, puede estar seguro de que el usuario tiene el control del número de teléfono proporcionado.

Normalmente, solo confirma la propiedad del número de teléfono una vez para un usuario. Al contrario de la autenticación de dos factores (2FA), en la que puede enviar un código SMS al usuario cada vez que inicia sesión. Twilio cuenta con una API Authy diferente para realizar comprobaciones de 2FA al iniciar sesión, pero no se cubrirá en esta publicación.

Tenga en cuenta que esta publicación utiliza la versión 1 de la API de Twilio Verify. La versión 2 de la API de verificación está actualmente en Beta.

Requisitos previos

Para seguir esta publicación, necesitará:

Puede encontrar el código completo para esta publicación en GitHub.

Creación del proyecto de estudio de caso

Con Visual Studio 2017+ o la CLI de .NET, cree una nueva solución y proyecto con las siguientes características:

  • Tipo: aplicación web ASP.NET Core 2.2 (no MVC) con Visual C#
  • Nombre: SendVerificaciónSmsonDemo
  • Directorio de soluciones
  • Repositorio de Git
  • https
  • Autenticación: cuentas de usuario individuales, almacenar cuentas de usuario en la app

ASP.NET Core Identity utiliza Entity Framework Core para almacenar a los usuarios en la base de datos, así que asegúrese de ejecutar las migraciones de bases de datos en la carpeta del proyecto después de crear la app. Ejecute una de las siguientes instrucciones de línea de comandos para crear la base de datos:

.NET CLI

dotnet ef database update

Package Manager Console

update-database

SDK de Twilio C# y la API de Twilio Verify

La API de Twilio es una API REST típica, pero para facilitar el trabajo con Twilio proporciona bibliotecas SDK auxiliares en diferentes idiomas. En publicaciones anteriores se mostró cómo utilizar C# SDK para validar los números de teléfono y cómo personalizarlo para que funcione con el contenedor de inyección de dependencia de ASP.NET Core.

Con la versión 1 de la API de Twilio Verify, puede realizar llamadas directamente a la API con solicitudes HTTP “sin procesar” mediante HttpClient en lugar de utilizar el SDK. El código básico que se requiere para hacerlo se describe en la documentación, pero para lograr condensación, se crea un HttpClient manualmente en el código. Debe evitar hacer esto en el código de producción.

En ASP.NET Core 2.1 y superior, debe utilizar HttpClientFactory siempre que sea posible. Esta clase administra la vida útil de los controladores y sockets subyacentes por usted, por lo que evita los problemas de rendimiento que puede tener en momentos de carga alta. Puede obtener más información acerca del uso de HttpClientFactory con el SDK de Twilio en una publicación anterior en el blog de Twilio.

Creación de un typed client para API de Twilio Verify

Para que sea más fácil trabajar con la API de verificación y admitir HttpClientFactory, creará un typed client pequeño. Cree el archivo TwilioVerifyClient.cs en la raíz de su proyecto y reemplace los contenidos con el siguiente código:

using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.AspNetCore.WebUtilities;
using Newtonsoft.Json;

namespace SendVerificationSmsDemo
{
   public class TwilioVerifyClient
   {
       private readonly HttpClient _client;
       public TwilioVerifyClient(HttpClient client)
       {
           _client = client;
       }

       public async Task<TwilioSendVerificationCodeResponse> StartVerification(int countryCode, string phoneNumber)
       {
           var requestContent = new FormUrlEncodedContent(new[] {
               new KeyValuePair<string, string>("via", "sms"),
               new KeyValuePair<string, string>("country_code", countryCode.ToString()),
               new KeyValuePair<string, string>("phone_number", phoneNumber),
           });

           var response = await _client.PostAsync("protected/json/phones/verification/start", requestContent);

           var content = await response.Content.ReadAsStringAsync();

           // this will throw if the response is not valid
           return JsonConvert.DeserializeObject<TwilioSendVerificationCodeResponse>(content);
       }

       public async Task<TwilioCheckCodeResponse> CheckVerificationCode(int countryCode, string phoneNumber, string verificationCode)
       {
           var queryParams = new Dictionary<string, string>()
           {
               {"country_code", countryCode.ToString()},
               {"phone_number", phoneNumber},
               {"verification_code", verificationCode },
           };

           var url = QueryHelpers.AddQueryString("protected/json/phones/verification/check", queryParams);

           var response = await _client.GetAsync(url);

           var content = await response.Content.ReadAsStringAsync();

           // this will throw if the response is not valid
           return JsonConvert.DeserializeObject<TwilioCheckCodeResponse>(content);
       }

       public class TwilioCheckCodeResponse
       {
           public string Message { get; set; }
           public bool Success { get; set; }
       }

       public class TwilioSendVerificationCodeResponse
       {
           public string Carrier { get; set; }
           public bool IsCellphone { get; set; }
           public string Message { get; set; }
           public string SecondsToExpire { get; set; }
           public Guid Uuid { get; set; }
           public bool Success { get; set; }
       }
   }
}

TwilioVerifyClient tiene dos métodos, StartVerification() y CheckVerificationCode(), que manejan la creación de solicitudes HTTP con el formato correcto y la llamada a la API de Twilio Verify. El cliente escrito acepta un HttpClient en su constructor, que será creado automáticamente por HttpClientFactory para usted.

Las respuestas para el método se implementan como objetos POCO simples que coinciden con la respuesta que devuelve la API de Twilio Verify. Para simplificar, se implementan aquí como clases anidadas de TwilioVerifyClient, pero puede moverlos a otro archivo si lo prefiere.

Configurar la autenticación del cliente escrito

HttpClientFactory no forma parte de las bibliotecas de base de ASP.NET Core, por lo que debe instalar el paquete Microsoft.Extensions.Http NuGet (versión 2.2.0 o posterior). Puede utilizar NuGet Package Manager, la CLI de la consola de Package Manager o editar el archivo SendVerificationSmsDemo.csproj. Después de utilizar cualquiera de estos métodos, la sección <ItemGroup> del archivo del proyecto debe verse así (los números de versión pueden ser más altos):

<ItemGroup>
 <PackageReference Include="Microsoft.AspNetCore.App" />
 <PackageReference Include="Microsoft.AspNetCore.Razor.Design" Version="2.2.0" PrivateAssets="All" />
 <PackageReference Include="Microsoft.Extensions.Http" Version="2.2.0" />
</ItemGroup>

Para llamar a API de Twilio Verify, necesitará la clave de API Authy para su aplicación de verificación (que se encuentra en la consola de Twilio). Cuando se desarrolla localmente, debe almacenar esta información con el administrador de información confidencial para que no se pueda enviar accidentalmente al repositorio de código fuente. Puede leer acerca de cómo y por qué hacerlo en esta publicación en el blog de Twilio. El secrets.json obtenido debe ser similar a este:

{
 "Twilio": {
   "VerifyApiKey": "DBxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
}

Por último, configure su TwilioVerifyClient con la BaseAddress correcta y su clave de API y agregue lo siguiente al final de ConfigureServices en Startup.cs:

public void ConfigureServices(IServiceCollection services)
{
   // existing configuration
  
   var apiKey = Configuration["Twilio:VerifyApiKey"];

   services.AddHttpClient<TwilioVerifyClient>(client =>
   {
       client.BaseAddress = new Uri("https://api.authy.com/");
       client.DefaultRequestHeaders.Add("X-Authy-API-Key", apiKey);
   });
}

No permita que la URL base de authy lo confunda: la versión 1 de la API de verificación siguió una estructura de interfaz que precedió la creación de Verify como un producto separado. La versión 2 de la API de verificación normaliza el URI.

Con la configuración del typed client completa, puede comenzar a agregar la funcionalidad de verificación telefónica a la aplicación ASP.NET Core Identity.

Agregar los archivos scaffolding requeridos

En esta publicación, agregará algunas páginas adicionales al área de identificación. Por lo general, cuando agrega o edita páginas de identificación en ASP.NET Core, debe utilizar las herramientas de scaffolding integradas para generar las páginas, como se muestra en esta publicación. Si ya lo ha hecho, puedes pasar por alto esta sección.

En lugar de agregar todos los scaffolding de identidad, todo lo que necesita para esta publicación es un archivo solo. Cree el archivo _ViewImports.cshtml en la carpeta Areas/Identity/Pages y agregue el siguiente código:

@using Microsoft.AspNetCore.Identity
@using SendVerificationSmsDemo.Areas.Identity
@namespace SendVerificationSmsDemo.Areas.Identity.Pages
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

Esto agrega todos los espacios de nombres y los auxiliares de etiquetas necesarios para sus Razor Pages. También iluminará IntelliSense en Visual Studio. Si ya realizó el scaffolding de las páginas de identificación, ya tendrá este archivo.

Enviar un código de verificación a un número de teléfono

Las plantillas de identificación de ASP.NET Core predeterminadas proporcionan la funcionalidad para almacenar un número de teléfono para un usuario, pero no proporcionan la capacidad de verificar la propiedad del número. En la publicación Validar los números telefónicos en Razor Pages de ASP.NET Core con Twilio Lookup, puede obtener más información sobre cómo validar un número de teléfono mediante la API de Twilio Lookup. Como se señaló en la publicación, es una buena idea almacenar el resultado como un número con formato E.164.

En la versión 1 de la API de verificación, debe proporcionar el código de marcado del país y el número de teléfono como parámetros separados. En consecuencia, si utiliza la versión 1 de API de verificación, es posible que sea mejor almacenar estos valores por separado para el usuario IdentityUser, en lugar de (o también) almacenar un número E.164.

Para simplificar, esta publicación pasará por alto el almacenamiento de los valores por separado. Creará un formulario que le permitirá al usuario ingresar los valores por separado. En la práctica, usaría automáticamente los valores adjuntos al usuario, en lugar de permitir que ingresen un nuevo número.

Cree una nueva Razor Page en la carpeta Areas/Identity/Pages/Account llamadaVerifyPhone.cdsl. En el archivo de código subyacente VerifyPhone.cshtml.cs, agregue lo siguiente mediante declaraciones en la parte superior del archivo:

using System.ComponentModel.DataAnnotations;
using Microsoft.AspNetCore.Authorization;

A continuación, reemplace la clase VerifyPhoneModel con lo siguiente:

Authorize]
public class VerifyPhoneModel : PageModel
{
   private readonly TwilioVerifyClient _client;

   public VerifyPhoneModel(TwilioVerifyClient client)
   {
       _client = client;
   }

   [BindProperty]
   public InputModel Input { get; set; }

   public class InputModel
   {
       [Required]
       [Display(Name = "Country dialing code")]
       public int DialingCode { get; set; }

       [Required]
       [Phone]
       [Display(Name = "Phone number")]
       public string PhoneNumber { get; set; }
   }
  
   public async Task<IActionResult> OnPostAsync()
   {
       if (!ModelState.IsValid)
       {
           return Page();
       }

       try
       {
           var result = await _client.StartVerification(Input.DialingCode

El InputModel para la página se utiliza para vincular un formulario simple (captura de pantalla a continuación) para recopilar el código de marcado del país y el número de teléfono que se debe verificar. El código no incluye un controlador OnGet ya que el marco proporciona uno de manera implícita. El controlador OnPost es donde comienza el proceso de verificación.

El código proporciona cierta validación básica mediante los atributos DataAnnotation (consulte la publicación Validar los números de teléfono en Razor Pages de ASP.NET Core Identity con Twilio Lookup para obtener más información sobre cómo realizar una validación sólida) y, si se realiza correctamente, utiliza TwilioVerifyClient inyectado para iniciar la verificación. Si la llamada de API de verificación es exitosa, el usuario es redirigido a la página ConfirmPhone, que creará en breve. Si la API de verificación indica que la solicitud falló o si se produce una excepción, se agrega un error a ModelState y se vuelve a mostrar la página al usuario.

El formulario consta de dos cuadros de texto y un botón para enviar. Reemplace el contenido de VerifyPhone.cshtml con la siguiente marca de Razor:

@page
@model VerifyPhoneModel
@{
   ViewData["Title"] = "Verify phone number";
}

<h4>@ViewData["Title"]</h4>
<div class="row">
   <div class="col-md-8">
       <form method="post">
           <div asp-validation-summary="ModelOnly" class="text-danger"></div>
           <div class="form-row">
               <div class="form-group col-md-4">
                   <label asp-for="Input.DialingCode"></label>
                   <input asp-for="Input.DialingCode" class="form-control" />
                   <span asp-validation-for="Input.DialingCode" class="text-danger"></span>
               </div>
               <div class="form-group col-md-8">
                   <label asp-for="Input.PhoneNumber"></label>
                   <input asp-for="Input.PhoneNumber" class="form-control" />
                   <span asp-validation-for="Input.PhoneNumber" class="text-danger"></span>
               </div>
           </div>
           <button type="submit" class="btn btn-primary">Send verification code</button>
       </form>
   </div>
</div>

@section Scripts {
   <partial name="_ValidationScriptsPartial" />
}

Cuando se muestra, el formulario se parece al siguiente:

Verifique el número de teléfono

Para probar el formulario, ejecute la app y vaya a /Identity/Account/VerifyPhone. Ingrese el código de su país y número de teléfono, y haga clic en Send verification code (Enviar código de verificación). Si el número de teléfono es válido, recibirá un SMS similar al mensaje que se muestra a continuación. Tenga en cuenta que puede personalizar este mensaje, incluido el idioma, la terminología y la longitud del código: consulte Documentación de la API de verificación para obtener más detalles.

Your Twilio Verify API demo verification code is: 2933

Ahora debe crear la página donde el usuario ingresa el código que recibe.

Comprobación del código de verificación

La página de comprobación del código de verificación contiene un solo cuadro de texto donde el usuario ingresa el código que recibe. Cree una nueva Razor Page en la carpeta Areas/Identity/Pages/Account llamada ConfirmPhone.cshtml. En el archivo de código subyacente ConfirmPhone.cshtml.cs, agregue lo siguiente mediante declaraciones en la parte superior del archivo:

using System.ComponentModel.DataAnnotations;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;

Luego, reemplace la clase ConfirmPhoneModel con lo siguiente:

Authorize]
public class ConfirmPhoneModel : PageModel
{
   private readonly TwilioVerifyClient _client;
   private readonly UserManager<IdentityUser> _userManager;

   public ConfirmPhoneModel(TwilioVerifyClient client

Al igual que antes, podemos omitir el controlador OnGet, ya que se proporciona implícitamente en el marco. El InputModel tiene tres parámetros: 1) el código de marcado, 2) el número de teléfono proporcionado en el paso anterior y 3) el código de verificación ingresado por el usuario.

Por razones de simplicidad, el código pasó el número de teléfono y el código de país del paso anterior a esta página a través de la querystring. En la práctica, los cargaría desde el propio IdentityUser, como se describió anteriormente.

Llamar a la API de Twilio Verify es sencillo gracias al typed client. El código de marcación y el número de teléfono del paso anterior y el código de autenticación ingresado por el usuario se pasan a la API. Si la comprobación es exitosa, la API de verificación dirá Result.Success=true. Puede almacenar el resultado de la confirmación en el objeto IdentityUser directamente configurando la propiedad PhoneNumberConfirmed y guardando los cambios.

Si todo se completa de manera exitosa, redirige al usuario a una página ConfirmPhoneSuccess simple (que creará en breve). Si hay algún error o excepción, se agrega un error al ModelState y se vuelve a mostrar la página.

Reemplace los contenidos de ConfirmPhone.cshtml con la marca de Razor que se indica a continuación. Para facilitar el uso, el número de teléfono proporcionado se muestra de nuevo en la página.

@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.Input.DialingCode) @Model.Input.PhoneNumber
               Enter the code you receive to confirm your phone number.
           </p>
           <div asp-validation-summary="All" class="text-danger"></div>
           <input asp-for="Input.DialingCode" type="hidden" />
           <input asp-for="Input.PhoneNumber" type="hidden" />

           <div class="form-group">
               <label asp-for="Input.VerificationCode"></label>
               <input asp-for="Input.VerificationCode" class="form-control" type="number" />
               <span asp-validation-for="Input.VerificationCode" class="text-danger"></span>
           </div>
           <button type="submit" class="btn btn-primary">Confirm</button>
       </form>
   </div>
</div>

@section Scripts {
   <partial name="_ValidationScriptsPartial" />
}

Una vez realizado, luce de la siguiente manera:

Pantalla de confirmación de número de teléfono

Una vez que el usuario confirma de manera exitosa su número de teléfono, puede estar seguro de que tiene acceso a él y puede utilizarlo con confianza en otras partes de su aplicación.

Mostrar una página de confirmación exitosa

Para crear una página de “felicitaciones” simple para el usuario, cree una nueva Razor Page en la carpeta Areas/Identity/Pages/Accoun llamada ConfirmPhoneSuccess.cshtml. No es necesario cambiar el código subyacente de esta página, simplemente agregue la siguiente marca a PhoneSuccess.cshtml:

@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>

Después de ingresar un código de verificación correcto, los usuarios serán redirigidos a esta página. Desde aquí, pueden volver a la página de inicio.

Pantalla de número de teléfono confirmado

Probar la funcionalidad Twilio Verify

Pruebe lo que acaba de crear ejecutando la app. Siga estos pasos para validar la propiedad de un usuario de un número de teléfono con la verificación:

Vaya a https://localhost:44348/Identity/Account/VerifyPhone en su navegador. Debido a que esta página está protegida por la autorización de ASP.NET Core Identity, será redirigido a la página de inicio de sesión de la cuenta.

Regístrese como un usuario nuevo. Será redirigido a la ruta /Identity/Account/VerifyPhone y verá la página Razor VerifyPhone.cshtml. En este punto, puede ver el registro del usuario que creó en la tabla dbo.AspNetUsers de la base de datos aspnet-SendVerificationSmasDemo-<GUID>. Tenga en cuenta que el número de teléfono es null.

Ingrese un código de país y un número de teléfono válidos para recibir mensajes de texto SMS. Haga clic en Send verification code (Enviar código de verificación). Debería enrutarse a un URI similar a /Identity/Account/ConfirmPhone?DialingCode=44&PhoneNumber=07123456789, donde el código de marcado y el número de teléfono reflejan los valores que ingresó.

En cuestión de momentos, debería recibir un mensaje SMS con un código de verificación. Tenga en cuenta que el mensaje refleja el nombre de la aplicación que creó en Twilio Verify.

En este punto, puede ir a la consola de Verify en https://www.twilio.com/console/verify/applications. Debe ver un valor de 1 en la columna SMS VERIFICATION STARTED (VERIFICACIÓN por SMS INICIADA). Seleccione la aplicación que coincida con el mensaje SMS que recibió.

Ingrese el código numérico del mensaje SMS en el cuadro Code (Código) de la página Confirm phone number (Confirmar número de teléfono) y haga clic en Confirm (Confirmar). (Los códigos de validación expiran, por lo que debe hacerlo en el plazo de los 10 minutos posteriores a la recepción del código).

Si todo funcionó correctamente, debe ser redirigido a la página /Identity/Account/ConfirmPhoneSucces. Si actualiza Verify Insights (Información de Verify) de su aplicación en la consola de Twilio, debería ver la validación correcta reflejada en las estadísticas de la aplicación.

¡Bien hecho! Integró con éxito Twilio Verify con ASP.NET Core 2.2 Identity.

Mejoras posibles

Esta publicación mostró el enfoque básico para utilizar la versión 1 de la API de verificación con ASP.NET Core Identity, pero existen muchas mejoras que podría realizar:

  • Almacene el código de marcado del país y el número de teléfono en IdentityUser. Esto sería necesario para las implementaciones prácticas, ya que desea asegurarse de que el número de teléfono almacenado en el usuario sea el que está verificando. Esto también simplificaría el código un poco, como se describió anteriormente.
  • Incluir un enlace a la página VerifyPhone. Actualmente, debe ir manualmente a Identity/Account/VerifyPhone, pero, en la práctica, es posible que desee agregar un enlace a este en algún lugar de la app.
  • Mostrar el estado de verificación del número de teléfono en la app. De manera predeterminada, ASP.NET Identity Core no muestra la propiedad IdentityUser.PhoneNumberConfirmed en ninguna parte de la app.
  • Solo verificar los números no confirmados. En relación con la mejora anterior, probablemente solo desee verificar los números de teléfono una vez, por lo que debe buscar PhoneNumberConfirmed=true en la página VerifyPhone, además de ocultar cualquier enlace de verificación.
  • Permitir el reenvío del código. En algunos casos, es posible que a los usuarios no les llegue el código de verificación. Para una experiencia de usuario más fluida, puede agregar una funcionalidad para permitir volver a enviar un código de confirmación a la página ConfirmPhone.

Resumen

En esta publicación, vio cómo utilizar la versión 1 de la API de Twilio Verify para confirmar la propiedad del número de teléfono en una aplicación ASP.NET Core Identity. Aprendió a crear un typed client para llamar a la API que utiliza las mejores prácticas, como HttpClientFactory, y cómo usarlo en Razor Pages. En este ejemplo, se tomó la vía simplista de pedir a los usuarios que vuelvan a ingresar el código de marcado y el número de teléfono de su país, pero en aplicaciones reales debe almacenarlos directamente en IdentityUser.

Puede encontrar el código de muestra completo para esta publicación en GitHub.

Este artículo fue traducido del original "Verifying Phone Number Ownership with Twilio using ASP.NET Core Identity and Razor Pages". Mientras estamos en nuestros procesos de traducción, nos encantaría recibir sus comentarios en help@twilio.com - las contribuciones valiosas pueden generar regalos de Twilio.