https://a.storyblok.com/f/270183/63332/124db23868/2fa-with-sms-or-email.png

Protegendo seu aplicativo ASP.NET com autenticação de dois fatores (2FA) usando SMS da Nexmo e e-mail da SendGrid

Publicado em May 17, 2021

Tempo de leitura: 6 minutos

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.

PM> Install-Package Nexmo.Csharp.Client

Instale o cliente SendGrid por meio do Gerenciador de Pacotes NuGet

PM> Install-Package SendGrid -Version 8.0.3

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!

Compartilhar:

https://a.storyblok.com/f/270183/150x150/a3d03a85fd/placeholder.svg
Sidharth Sharma