
Protegendo seu aplicativo ASP.NET com autenticação de dois fatores (2FA) usando SMS da Nexmo e e-mail da SendGrid
A autenticação de dois fatores (2FA) é hoje em dia imprescindível para aumentar a segurança do seu aplicativo. Ela está presente em todos os tipos de applications: desde o processo de cadastro até a verificação das ações do usuário. Os tipos mais comuns de 2FA são a verificação por telefone e a verificação por e-mail.
Neste tutorial, mostraremos como configurar a autenticação de dois fatores (2FA) em seu aplicativo .NET usando o ASP .NET Identity, a Biblioteca de Cliente Nexmo para C# para autenticação por SMS e a cliente C# da SendGrid para autenticação por e-mail.
Se você quiser apenas ver o resultado, pode dar uma olhada no o vídeo ou baixar o código.
Configurar um aplicativo ASP .NET MVC
Abra o Visual Studio e crie um novo aplicativo ASP .NET MVC. Para esta demonstração, vamos excluir as seções “Contato” e “Sobre” do site gerado por padrão.
Instale o Nexmo Client em seu aplicativo por meio do Gerenciador de Pacotes NuGet
Adicione o Nexmo Client ao seu aplicativo por meio do Console de Pacotes do NuGet.
Instale o cliente SendGrid por meio do Gerenciador de Pacotes NuGet
Adicionar credenciais do Nexmo e do SendGrid
Para fins de demonstração, colocaremos as credenciais do Nexmo e do SendGrid na <appSettings> seção do Web.config arquivo. Se estivéssemos desenvolvendo esse aplicativo para distribuição, poderíamos optar por inserir essas credenciais no nosso portal do 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" />
Configure o Nexmo no serviço de SMS e o SendGrid no serviço de e-mail
Dentro do IdentityConfig.cs arquivo, adicione a configuração do SendGrid no SMSService método. Em seguida, insira o Nexmo Client dentro do SMSService método do IdentityConfig.cs arquivo.
Lembre-se de adicionar as diretivas “using” para o Nexmo.Api e SendGrid namespaces, bem como quaisquer outros namespaces que estejam sinalizados como ausentes.
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);
}
} Adicione o método 'SendEmailConfirmationTokenAsync()' ao 'AccountController'
Adicione o seguinte método ao seu AccountController que será chamado no momento do cadastro do usuário para enviar um e-mail de confirmação para o endereço de e-mail fornecido.
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;
}
Atualizar o método de ação 'Register'
No interior do Register método do AccountController, adicione algumas propriedades à variável recém-criada do tipo ApplicationUser: TwoFactorEnabled (true), PhoneNumberConfirmed (false). Assim que o usuário for criado com sucesso, armazene o ID do usuário no estado da sessão e redirecione o usuário para o AddPhoneNumber método de ação no 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);
}
Verifique no banco de dados se o número de telefone já existe e adicione a lógica de SMS ao método de ação AddPhoneNumber
No ManageController adicionar o [AllowAnonymous] atributo aos métodos GET e POST AddPhoneNumber . Isso permite que o usuário, que ainda não está cadastrado, tenha acesso ao fluxo de trabalho de confirmação do número de telefone. Faça uma consulta ao banco de dados para verificar se o número de telefone inserido pelo usuário já está associado a uma conta. Caso contrário, redirecione o usuário para o VerifyPhoneNumber método de ação.
[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();
}
}
Atualizar o método de ação VerifyPhoneNumber
Adicione o [AllowAnonymous] atributo ao método de ação GET e exclua tudo do método, exceto a instrução de retorno que direciona o fluxo de verificação,
[AllowAnonymous]
public async Task<ActionResult> VerifyPhoneNumber(string phoneNumber)
{
return phoneNumber == null ? View("Error") : View(new VerifyPhoneNumberViewModel { PhoneNumber = phoneNumber });
}
Substituir User.Identity.GetUserId() por Session["UserID"] no método, conforme mostrado abaixo. Se o usuário digitar o código PIN corretamente, ele será direcionado para a visualização Índice do ManageController. A propriedade booleana do usuário PhoneNumberConfirmed é então definida como 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);
}
Verificar se o usuário possui um e-mail confirmado ao fazer login
Voltando ao AccountController, atualize o Login() método de ação para verificar se o usuário confirmou seu e-mail ou não. Caso contrário, retorne uma mensagem de erro e redirecione o usuário para a visualização “Informações”. Além disso, chame o SendEmailConfirmationTokenAsync() método, passando o user.Id e um assunto do e-mail.
[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");
}
}
...
Exibir informações adicionais
Dentro da Views/Account, crie uma nova Visualização chamada Info para a qual o usuário será redirecionado caso seu e-mail não tenha sido confirmado. A view deve conter o seguinte código:
<h2>@ViewBag.Title.</h2>
<h3>@ViewBag.Message</h3> Garanta que a autenticação de dois fatores (2FA) não possa ser contornada
No Views/Account/Login.cshtml exclua a <div class="form-group"> que contém a caixa de seleção “Lembrar-me”. Em Views/Account/VerifyCode.cshtml exclua o <div class="form-group"> da caixa de seleção “RememberBrowser” e do campo oculto “RememberMe”. Exclua a variável correspondente em cada um dos modelos de visualização em AccountViewModels.cs: SendCodeViewModel e VerifyCodeViewModel. Por fim, remova qualquer uso dessas variáveis (incluindo assinaturas de métodos) ou, quando necessário, substitua o uso dessas variáveis nos dois por false. Isso impedirá que o usuário contorne a verificação de autenticação de duas etapas (2FA).
Conclusão
Com isso, você tem um aplicativo web que utiliza o ASP .NET Identity e que está habilitado para a autenticação de dois fatores (2FA), utilizando o SMS da Nexmo e o e-mail da SendGrid como métodos diferentes de verificação.
O SMS e o e-mail oferecem camadas adicionais de segurança para identificar corretamente os usuários e proteger ainda mais as informações confidenciais deles. Usando a biblioteca de clientes Nexmo em C# e o cliente SendGrid em C#, você pode adicionar facilmente a verificação por SMS e por e-mail.
Por favor, baixe o código e teste você mesmo.
Fique à vontade para me enviar quaisquer comentários ou perguntas no Twitter @sidsharma_27 ou por e-mail para sidharth.sharma@nexmo.com!