
Cómo proteger su aplicación ASP.NET con 2FA mediante Nexmo SMS y SendGrid Email
2FA (2 Factor Authentication) es una necesidad hoy en día para aumentar la seguridad dentro de su aplicación. Se ve en todo tipo de aplicaciones: desde el proceso de registro hasta la verificación de la acción del usuario. Los tipos más comunes de 2FA son la verificación por teléfono y la verificación por correo electrónico.
En este tutorial mostraremos cómo configurar 2FA en su aplicación .NET utilizando ASP .NET Identity, la Nexmo C# Client Library para autenticación por SMS y el SendGrid C# Cliente para autenticación por correo electrónico.
Si sólo quieres ver el resultado puedes echar un vistazo al el Video o coger el código.
Configurar una aplicación ASP .NET MVC
Abra Visual Studio y cree una nueva aplicación ASP .NET MVC. Para esta demostración, eliminaremos las secciones Contacto y Acerca de del sitio web generado por defecto.
Instale el Cliente Nexmo en su aplicación a través de NuGet Package Manager
Añada el Cliente Nexmo a su aplicación a través de la Consola de Paquetes NuGet.
Instalar el cliente SendGrid a través de NuGet Package Manager
Añadir credenciales de Nexmo y SendGrid
Para los fines de la demostración, pondremos las credenciales de Nexmo y SendGrid en la sección <appSettings> del archivo Web.config del archivo. Si estuviéramos desarrollando esta aplicación para su distribución podríamos optar por introducir estas credenciales en nuestro portal de Azure.
<add key="Nexmo.Url.Rest" value="https://rest.nexmo.com" />
<add key="Nexmo.Url.Api" value="https://api.nexmo.com" />
<add key="Nexmo.api_key" value="NEXMO_API_KEY" />
<add key="Nexmo.api_secret" value="NEXMO_API_SECRET" />
<add key="SMSAccountFrom" value="SMS_FROM_NUMBER" />
<add key="mailAccount" value="SENDGRID_API_KEY" />
Conecte Nexmo en el servicio SMS, SendGrid en el servicio Email
Dentro del archivo IdentityConfig.cs añada la configuración de SendGrid en el método SMSService método. Luego, conecte el Cliente Nexmo dentro del método SMSService del archivo IdentityConfig.cs archivo.
Recuerde añadir las directivas using para los archivos Nexmo.Api y SendGrid y cualquier otro espacio de nombres que falte.
public class EmailService : IIdentityMessageService
{
public async Task SendAsync(IdentityMessage message)
{
// Plug in your email service here to send an email.
await configSendGridasync(message);
}
private async Task configSendGridasync(IdentityMessage message)
{
string apiKey = ConfigurationManager.AppSettings["mailAPIKey"];
dynamic sg = new SendGridAPIClient(apiKey, "https://api.sendgrid.com");
Email from = new Email("demo@nexmo.com");
string subject = message.Subject;
Email to = new Email(message.Destination);
Content content = new Content("text/plain", message.Body);
Mail mail = new Mail(from, subject, to, content);
dynamic response = await sg.client.mail.send.post(requestBody: mail.Get());
}
}
public class SmsService : IIdentityMessageService
{
public Task SendAsync(IdentityMessage message)
{
var sms = SMS.Send(new SMS.SMSRequest
{
from = ConfigurationManager.AppSettings["SMSAccountFrom"],
to = message.Destination,
text = message.Body
});
return Task.FromResult(0);
}
} Añadir el método "SendEmailConfirmationTokenAsync()" a "AccountController".
Añada el siguiente método a su AccountController que será llamado al registrarse el usuario para enviar un correo electrónico de confirmación a la dirección de correo electrónico proporcionada.
private async Task<string> SendEmailConfirmationTokenAsync(string userID, string subject)
{
string code = await UserManager.GenerateEmailConfirmationTokenAsync(userID);
var callbackUrl = Url.Action("ConfirmEmail", "Account", new { userId = userID, code = code }, protocol: Request.Url.Scheme);
await UserManager.SendEmailAsync(userID, subject, "Please confirm your account by clicking <a href="" + callbackUrl + "">here</a>");
return callbackUrl;
}
Actualizar el método de acción "Registrar
Dentro del método Register del método AccountControllerañade un par de propiedades a la nueva variable creada de tipo ApplicationUser: TwoFactorEnabled (true), PhoneNumberConfirmed (false). Una vez que el usuario se ha creado correctamente, almacena el ID de usuario en un estado de sesión y redirige al usuario al método AddPhoneNumber en el método ManageController.
[AllowAnonymous]
public ActionResult AddPhoneNumber()
{
return View();
}
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Register(RegisterViewModel model)
{
if (ModelState.IsValid)
{
var user = new ApplicationUser { UserName = model.Email, Email = model.Email, TwoFactorEnabled = true, PhoneNumberConfirmed = false};
var result = await UserManager.CreateAsync(user, model.Password);
if (result.Succeeded)
{
Session["UserID"] = user.Id;
return RedirectToAction("AddPhoneNumber", "Manage");
}
AddErrors(result);
}
// If we got this far, something failed, redisplay form
return View(model);
}
Comprobar la base de datos para el número de teléfono existente y añadir la lógica de SMS a la AddPhoneNumber método de acción
En la sección ManageController añada el atributo [AllowAnonymous] a los métodos de acción GET y POST AddPhoneNumber de la acción. Esto le da al usuario no registrado acceso al flujo de trabajo de confirmación del número de teléfono. Realice una consulta a la base de datos para comprobar si el número de teléfono introducido por el usuario está previamente asociado a una Account. Si no es así, redirija al usuario al método de acción VerifyPhoneNumber método de acción.
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> AddPhoneNumber(AddPhoneNumberViewModel model)
{
if (!ModelState.IsValid)
{
return View(model);
}
var db = new ApplicationDbContext();
if (db.Users.FirstOrDefault(u => u.PhoneNumber == model.Number) == null)
{
// Generate the token and send it
var code = await UserManager.GenerateChangePhoneNumberTokenAsync((string)Session["UserID"], model.Number);
if (UserManager.SmsService != null)
{
var message = new IdentityMessage
{
Destination = model.Number,
Body = "Your security code is: " + code
};
await UserManager.SmsService.SendAsync(message);
}
return RedirectToAction("VerifyPhoneNumber", new { PhoneNumber = model.Number });
}
else
{
ModelState.AddModelError("", "The provided phone number is associated with another account.");
return View();
}
}
Actualizar el método de acción VerifyPhoneNumber
Añada el atributo [AllowAnonymous] al método de acción GET y elimine todo en el método excepto la sentencia return que dirige el flujo de verificación,
[AllowAnonymous]
public async Task<ActionResult> VerifyPhoneNumber(string phoneNumber)
{
return phoneNumber == null ? View("Error") : View(new VerifyPhoneNumberViewModel { PhoneNumber = phoneNumber });
}
Sustituya User.Identity.GetUserId() por Session["UserID"] en el método como se muestra a continuación. Si el usuario introduce correctamente el código pin, se le redirige a la vista Índice de la aplicación ManageController. La propiedad booleana del usuario PhoneNumberConfirmed se establece en true.
[AllowAnonymous]
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> VerifyPhoneNumber(VerifyPhoneNumberViewModel model)
{
if (!ModelState.IsValid)
{
return View(model);
}
var result = await UserManager.ChangePhoneNumberAsync((string)Session["UserID"], model.PhoneNumber, model.Code);
if (result.Succeeded)
{
var user = await UserManager.FindByIdAsync((string)Session["UserID"]);
if (user != null)
{
await SignInManager.SignInAsync(user, isPersistent: false, rememberBrowser: false);
}
return RedirectToAction("Index", new { Message = ManageMessageId.AddPhoneSuccess });
}
// If we got this far, something failed, redisplay form
ModelState.AddModelError("", "Failed to verify phone");
return View(model);
}
Comprobar si el usuario tiene un correo electrónico confirmado en Login
De nuevo en el AccountControlleractualice el método de acción Login() para comprobar si el usuario ha confirmado su correo electrónico o no. Si no es así, devuelve un mensaje de error y redirige al usuario a la vista "Información". Además, llama al método SendEmailConfirmationTokenAsync() pasando el valor user.Id y un asunto de correo electrónico.
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Login(LoginViewModel model, string returnUrl)
{
if (!ModelState.IsValid)
{
return View(model);
}
var user = await UserManager.FindByNameAsync(model.Email);
if (user != null)
{
if (!await UserManager.IsEmailConfirmedAsync(user.Id))
{
string callbackUrl = await SendEmailConfirmationTokenAsync(user.Id, "Confirm your account");
ViewBag.title = "Check Email";
ViewBag.message = "You must have a confirmed email to login.";
return View("Info");
}
}
...
Añadir vista de información
Dentro de Views/Accountcree una nueva vista llamada Info a la que el usuario será redirigido si su correo electrónico no ha sido confirmado. La vista debe contener el siguiente código:
<h2>@ViewBag.Title.</h2>
<h3>@ViewBag.Message</h3> Asegúrese de que 2FA no puede ser eludido
En el Views/Account/Login.cshtml elimine la casilla <div class="form-group"> que contiene la casilla "Recordarme". En Views/Account/VerifyCode.cshtml elimine el <div class="form-group"> que contiene la casilla de verificación "RememberBrowser" y la entrada oculta RememberMe. Eliminar la variable correspondiente en cada uno de los modelos de vista en AccountViewModels.cs: SendCodeViewModel y VerifyCodeViewModel. Por último, elimine cualquier uso de estas variables (incluidas las firmas de métodos) o, cuando sea necesario, sustituya el uso de estas variables en los dos con false. Esto impedirá que el usuario eluda la verificación 2FA.
Conclusión
Con eso, usted tiene una aplicación web usando ASP .NET Identity que es 2 Factor Authentication (2FA) habilitado usando Nexmo SMS y SendGrid Email como los diferentes métodos de verificación.
Los SMS y el correo electrónico proporcionan capas adicionales de seguridad para identificar correctamente a los usuarios y proteger aún más la información confidencial de los usuarios. Usando la librería Nexmo C# Client Library y el cliente C# de SendGrid, puede añadir verificación por SMS y correo electrónico con facilidad.
Por favor obtener el código y pruébelo usted mismo.
No dude en enviarme cualquier pregunta o comentario a través de Twitter @sidsharma_27 o envíeme un correo electrónico a sidharth.sharma@nexmo.com¡!