Compartilhar via


Confirmação da conta e recuperação de senha com ASP.NET Identity (C#)

Antes de fazer este tutorial, primeiro você deve concluir Criar um aplicativo Web seguro ASP.NET MVC 5 com logon, confirmação de email e redefinição de senha. Este tutorial contém mais detalhes e mostrará como configurar emails para confirmação de conta local e permitir que os usuários redefinam sua senha esquecida no ASP.NET Identity.

Uma conta de usuário local exige que o usuário crie uma senha para a conta e essa senha é armazenada (com segurança) no aplicativo Web. ASP.NET Identity também dá suporte a contas sociais, que não exigem que o usuário crie uma senha para o aplicativo. Contas de redes sociais usam um terceiro (como Google, Twitter, Facebook ou Microsoft) para autenticar usuários. Este tópico aborda o seguinte:

Novos usuários registram seu alias de email, que cria uma conta local.

Imagem da janela de registro da conta

Selecionar o botão Registrar envia um email de confirmação contendo um token de validação para seu endereço de email.

Imagem mostrando a confirmação de email enviado

O usuário recebe um email com um token de confirmação para sua conta.

Imagem do token de confirmação

Selecionar o link confirma a conta.

Imagem confirmando o endereço de email

Recuperação/redefinição de senha

Os usuários locais que esquecem sua senha podem ter um token de segurança enviado para sua conta de email, permitindo que eles redefinam sua senha.

Imagem da janela de redefinição de senha esquecida

Em breve, o usuário receberá um email com um link permitindo que ele redefina sua senha.

Imagem mostrando o email de redefinição de senha
Selecionar o link levará-os para a página de Redefinição.

Imagem mostrando a janela de redefinição de senha do usuário

Ao selecionar o botão Redefinir, você confirma que a senha foi redefinida.

Imagem mostrando confirmação de redefinição de senha

Criar um aplicativo Web ASP.NET

Comece instalando e executando Visual Studio 2017.

  1. Crie um novo projeto web ASP.NET e selecione o modelo MVC. Os Web Forms também dão suporte ao ASP.NET Identity, para que você possa seguir etapas semelhantes em um aplicativo de formulários Web.

  2. Altere a autenticação para Contas de usuário individuais.

  3. Execute o aplicativo, selecione o link Registrar e registre um usuário. Neste ponto, a única validação no email é com o atributo [EmailAddress].

  4. No Gerenciador de Servidores, navegue até Conexões de Dados\DefaultConnection\Tables\AspNetUsers, clique com o botão direito do mouse e selecione Abrir definição de tabela.

    A imagem a seguir mostra o esquema AspNetUsers:

    imagem Imagem mostrando o esquema de usuários ASP Net

  5. Clique com o botão direito do mouse na tabela AspNetUsers e selecione Mostrar Dados da Tabela.

    imagem mostrando dados de tabela

    Neste ponto, o email não foi confirmado.

O armazenamento de dados padrão para ASP.NET Identity é o Entity Framework, mas você pode configurá-lo para usar outros armazenamentos de dados e adicionar campos adicionais. Consulte a seção Recursos Adicionais no final deste tutorial.

A classe de inicialização OWIN ( Startup.cs) é chamada quando o aplicativo inicia e invoca o método ConfigureAuth em App_Start\Startup.Auth.cs, que configura o pipeline OWIN e inicializa ASP.NET Identity. Examine o método ConfigureAuth. Cada chamada CreatePerOwinContext registra um callback (salvo no OwinContext) que será acionada uma vez por solicitação para criar uma instância do tipo especificado. Você pode definir um ponto de interrupção no construtor e no método Create de cada tipo (ApplicationDbContext, ApplicationUserManager) e verificar se eles são chamados em cada solicitação. Uma instância de ApplicationDbContext e ApplicationUserManager é armazenada no contexto OWIN, que pode ser acessado em todo o aplicativo. ASP.NET Identity conecta-se ao pipeline OWIN por meio do middleware de cookie. Para mais informações, consulte Gerenciamento de tempo de vida por solicitação para a classe UserManager no ASP.NET Identity.

Quando você altera o perfil de segurança, um novo carimbo de segurança é gerado e armazenado no campo SecurityStamp da tabela aspNetUsers. Observe que o campo SecurityStamp é diferente do cookie de segurança. O cookie de segurança não é armazenado na tabela AspNetUsers (ou em qualquer outro lugar no Banco de Dados de Identidade). O token de cookie de segurança é autoassinado usando DPAPI e é criado com as informações de UserId, SecurityStamp e de tempo de expiração.

O middleware de cookie verifica o cookie em cada solicitação. O método SecurityStampValidator na classe Startup acessa o BD e verifica o carimbo de segurança periodicamente, como especificado com o validateInterval. Isso só acontece a cada 30 minutos (em nosso exemplo), a menos que você altere seu perfil de segurança. O intervalo de 30 minutos foi escolhido para minimizar as viagens ao banco de dados. Consulte meu tutorial de autenticação de dois fatores para obter mais detalhes.

De acordo com os comentários no código, o método UseCookieAuthentication dá suporte à autenticação de cookie. O campo SecurityStamp e o código associado fornecem uma camada extra de segurança para seu aplicativo, quando você altera sua senha, você será desconectado do navegador com o qual fez logon. O método SecurityStampValidator.OnValidateIdentity permite que o aplicativo valide o token de segurança quando o usuário faz logon, que é usado quando você altera uma senha ou usa o logon externo. Isso é necessário para garantir que todos os tokens (cookies) gerados com a senha antiga sejam invalidados. No projeto de exemplo, se você alterar a senha dos usuários, um novo token será gerado para o usuário, todos os tokens anteriores serão invalidados e o campo SecurityStamp será atualizado.

O sistema de identidade permite que você configure seu aplicativo para que, quando o perfil de segurança dos usuários for alterado (por exemplo, quando o usuário altera sua senha ou altera o logon associado (como do Facebook, Google, conta da Microsoft etc.), o usuário é desconectado de todas as instâncias do navegador. Por exemplo, a imagem abaixo mostra o aplicativo de exemplo de Logon único, que permite que o usuário saia de todas as instâncias do navegador (nesse caso, IE, Firefox e Chrome) selecionando um botão. Como alternativa, o exemplo permite que fazer logoff somente de uma instância específica do navegador.

Imagem mostrando a janela do aplicativo de exemplo de logoff único

O aplicativo de exemplo de Logon único mostra como o ASP.NET Identity permite regenerar o token de segurança. Isso é necessário para garantir que todos os tokens (cookies) gerados com a senha antiga sejam invalidados. Esse recurso fornece uma camada extra de segurança para seu aplicativo; ao alterar sua senha, você será desconectado de onde fez logon neste aplicativo.

O arquivo App_Start\IdentityConfig.cs contém as classes ApplicationUserManager, EmailService e SmsService. As classes EmailService e SmsService implementam cada uma a interface IIdentityMessageService, portanto, você tem métodos comuns em cada classe para configurar email e SMS. Embora este tutorial mostre apenas como adicionar notificação por email por meio de SendGrid, você pode enviar emails usando SMTP e outros mecanismos.

A classe Startup também contém código padrão para adicionar logins sociais (Facebook, Twitter, etc.), consulte meu tutorial Aplicativo MVC 5 com Facebook, Twitter, LinkedIn e Google OAuth2 Login para obter mais informações.

Examine a classe ApplicationUserManager, que contém as informações de identidade dos usuários e configura os seguintes recursos:

  • Requisitos de força de senha.
  • Bloqueio do usuário (tentativas e duração).
  • Autenticação de dois fatores (2FA). Abordarei 2FA e SMS em outro tutorial.
  • Configurando serviços de email e SMS. (Abordarei SMS em outro tutorial).

A classe ApplicationUserManager deriva da classe UserManager<ApplicationUser> genérica. ApplicationUser deriva de IdentityUser. IdentityUser deriva da classe IdentityUser genérica:

//     Default EntityFramework IUser implementation
public class IdentityUser<TKey, TLogin, TRole, TClaim> : IUser<TKey>
   where TLogin : IdentityUserLogin<TKey>
   where TRole : IdentityUserRole<TKey>
   where TClaim : IdentityUserClaim<TKey>
{
   public IdentityUser()
   {
      Claims = new List<TClaim>();
      Roles = new List<TRole>();
      Logins = new List<TLogin>();
   }

   ///     User ID (Primary Key)
   public virtual TKey Id { get; set; }

   public virtual string Email { get; set; }
   public virtual bool EmailConfirmed { get; set; }

   public virtual string PasswordHash { get; set; }

   ///     A random value that should change whenever a users credentials have changed (password changed, login removed)
   public virtual string SecurityStamp { get; set; }

   public virtual string PhoneNumber { get; set; }
   public virtual bool PhoneNumberConfirmed { get; set; }

   public virtual bool TwoFactorEnabled { get; set; }

   ///     DateTime in UTC when lockout ends, any time in the past is considered not locked out.
   public virtual DateTime? LockoutEndDateUtc { get; set; }

   public virtual bool LockoutEnabled { get; set; }

   ///     Used to record failures for the purposes of lockout
   public virtual int AccessFailedCount { get; set; }
   
   ///     Navigation property for user roles
   public virtual ICollection<TRole> Roles { get; private set; }

   ///     Navigation property for user claims
   public virtual ICollection<TClaim> Claims { get; private set; }

   ///     Navigation property for user logins
   public virtual ICollection<TLogin> Logins { get; private set; }
   
   public virtual string UserName { get; set; }
}

As propriedades acima coincidem com as propriedades na tabela AspNetUsers, mostradas acima.

Argumentos genéricos em IUser permitem derivar uma classe usando tipos diferentes para a chave primária. Consulte o exemplo ChangePK que mostra como alterar a chave primária de cadeia de caracteres para int ou GUID.

ApplicationUser

ApplicationUser (public class ApplicationUserManager : UserManager<ApplicationUser>) é definido em Models\IdentityModels.cs como:

public class ApplicationUser : IdentityUser
{
    public async Task<ClaimsIdentity> GenerateUserIdentityAsync(
        UserManager<ApplicationUser> manager)
    {
        // Note the authenticationType must match the one defined in 
       //   CookieAuthenticationOptions.AuthenticationType
        var userIdentity = await manager.CreateIdentityAsync(this, 
    DefaultAuthenticationTypes.ApplicationCookie);
        // Add custom user claims here
        return userIdentity;
    }
}

O código destacado acima gera um ClaimsIdentity. ASP.NET Identity e OWIN Cookie Authentication são baseados em declarações, portanto, o framework requer que o app gere um ClaimsIdentity para o usuário. ClaimsIdentity tem informações sobre todas as declarações do usuário, como o nome do usuário, a idade e a quais funções o usuário pertence. Você também pode adicionar mais declarações para o usuário neste estágio.

O método OWIN AuthenticationManager.SignIn passa no ClaimsIdentity e autentica o usuário:

private async Task SignInAsync(ApplicationUser user, bool isPersistent)
{
    AuthenticationManager.SignOut(DefaultAuthenticationTypes.ExternalCookie);
    AuthenticationManager.SignIn(new AuthenticationProperties(){
       IsPersistent = isPersistent }, 
       await user.GenerateUserIdentityAsync(UserManager));
}

Aplicativo MVC 5 com funcionalidade de Logon do Facebook, Twitter, LinkedIn e Google OAuth2, mostra como você pode adicionar novas propriedades à classe ApplicationUser.

Confirmação de email

É uma boa ideia confirmar o email com o qual um novo usuário se registra para verificar se ele não está se passando por outra pessoa (ou seja, eles não se registraram com o email de outra pessoa). Suponha que você tenha um fórum de discussão, você gostaria de impedir que "bob@example.com" se registrassem como "joe@contoso.com". Sem confirmação por email, "joe@contoso.com" pode receber emails indesejados do seu aplicativo. Suponha que Bob acidentalmente se registrou como "bib@example.com" e não tinha notado, ele não seria capaz de usar a recuperação de senha porque o aplicativo não tem seu email correto. A confirmação de email fornece apenas proteção limitada contra bots e não fornece proteção contra spammers determinados, eles têm muitos aliases de email que podem ser usados para se registrar. No exemplo abaixo, o usuário não poderá alterar sua senha até que sua conta seja confirmada (selecionando um link de confirmação recebido na conta de email com a qual se registrou.) Você pode aplicar esse fluxo de trabalho a outros cenários, por exemplo, enviando um link para confirmar e redefinir a senha em novas contas criadas pelo administrador, enviando um email ao usuário quando ele tiver alterado seu perfil e assim por diante. Você geralmente deseja impedir que novos usuários postem dados em seu site antes de serem confirmados por email, uma mensagem de texto SMS ou outro mecanismo.

Criar um exemplo mais completo

Nesta seção, você usará o NuGet para baixar um exemplo mais completo com o qual trabalharemos.

  1. Criar um novo projeto empty ASP.NET Web.

  2. No Console do Gerenciador de Pacotes, insira os seguintes comandos:

    Install-Package SendGrid
    Install-Package -Prerelease Microsoft.AspNet.Identity.Samples
    

    Neste tutorial, usaremos SendGrid para enviar emails. O pacote Identity.Samples instala o código com o qual trabalharemos.

  3. Defina o projeto para usar SSL.

  4. Teste a criação da conta local executando o aplicativo, selecionando o link Registrar e postando o formulário de registro.

  5. Selecione o link de email de demonstração, que simula a confirmação de email.

  6. Remova o código de confirmação do link de email de teste do exemplo (o código ViewBag.Link no controlador de conta. Consulte os métodos de ação DisplayEmail e ForgotPasswordConfirmation e exibições Razor).

Aviso

Se você alterar qualquer uma das configurações de segurança neste exemplo, os aplicativos de produção precisarão passar por uma auditoria de segurança que chame explicitamente as alterações feitas.

Examine o código em App_Start\IdentityConfig.cs

O exemplo mostra como criar uma conta e adicioná-la à função administrador. Você deve substituir o email no exemplo pelo email que usará para a conta de administrador. A maneira mais fácil agora de criar uma conta de administrador é programaticamente no método Seed. Esperamos ter uma ferramenta no futuro que permita que você crie e administre usuários e funções. O código de exemplo permite que você crie e gerencie usuários e funções, mas primeiro você deve ter uma conta de administradores para executar as funções e as páginas de administrador do usuário. Neste exemplo, a conta de administrador é criada quando o banco de dados é inicializado.

Altere a senha e altere o nome para uma conta em que você possa receber notificações por email.

Aviso

Segurança – Nunca armazene dados confidenciais no código-fonte.

Como já foi mencionado, a chamada app.CreatePerOwinContext na classe de inicialização adiciona retornos de chamada ao método Create das classes de conteúdo do banco de dados do aplicativo, do gerenciador de usuários e do gerenciador de funções. O pipeline OWIN chama o método Create nessas classes para cada solicitação e armazena o contexto para cada classe. O controlador de conta expõe o gerenciador de usuários do contexto HTTP (que contém o contexto OWIN):

public ApplicationUserManager UserManager
{
    get
    {
        return _userManager ?? 
    HttpContext.GetOwinContext().GetUserManager<ApplicationUserManager>();
    }
    private set
    {
        _userManager = value;
    }
}

Quando um usuário registra uma conta local, o método HTTP Post Register é chamado:

[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Register(RegisterViewModel model)
{
    if (ModelState.IsValid)
    {
        var user = new ApplicationUser { UserName = model.Email, Email = model.Email };
        var result = await UserManager.CreateAsync(user, model.Password);
        if (result.Succeeded)
        {
            var code = await UserManager.GenerateEmailConfirmationTokenAsync(user.Id);
            var callbackUrl = Url.Action(
               "ConfirmEmail", "Account", 
               new { userId = user.Id, code = code }, 
               protocol: Request.Url.Scheme);

            await UserManager.SendEmailAsync(user.Id, 
               "Confirm your account", 
               "Please confirm your account by clicking this link: <a href=\"" 
                                               + callbackUrl + "\">link</a>");
            // ViewBag.Link = callbackUrl;   // Used only for initial demo.
            return View("DisplayEmail");
        }
        AddErrors(result);
    }

    // If we got this far, something failed, redisplay form
    return View(model);
}

O código acima usa os dados do modelo para criar uma nova conta de usuário usando o email e a senha inseridos. Se o alias de email estiver no armazenamento de dados, a criação da conta falhará e o formulário será exibido novamente. O método GenerateEmailConfirmationTokenAsync cria um token de confirmação seguro e o armazena no armazenamento de dados do ASP.NET Identity. O método Url.Action cria um link que contém o UserId e o token de confirmação. Esse link é enviado por email para o usuário, o usuário pode selecionar no link em seu aplicativo de email para confirmar sua conta.

Configurar confirmação de email

Acesse a página de inscrição do SendGrid e registre-se para obter uma conta gratuita. Adicione um código semelhante ao seguinte para configurar o SendGrid:

public class EmailService : IIdentityMessageService
{
   public Task SendAsync(IdentityMessage message)
   {
      return configSendGridasync(message);
   }

   private Task configSendGridasync(IdentityMessage message)
   {
      var myMessage = new SendGridMessage();
      myMessage.AddTo(message.Destination);
      myMessage.From = new System.Net.Mail.MailAddress(
                          "Joe@contoso.com", "Joe S.");
      myMessage.Subject = message.Subject;
      myMessage.Text = message.Body;
      myMessage.Html = message.Body;

      var credentials = new NetworkCredential(
                 ConfigurationManager.AppSettings["mailAccount"],
                 ConfigurationManager.AppSettings["mailPassword"]
                 );

      // Create a Web transport for sending email.
      var transportWeb = new Web(credentials);

      // Send the email.
      if (transportWeb != null)
      {
         return transportWeb.DeliverAsync(myMessage);
      }
      else
      {
         return Task.FromResult(0);
      }
   }
}

Nota

Clientes de email frequentemente aceitam apenas mensagens de texto (sem HTML). Você deve fornecer a mensagem em texto e HTML. No exemplo de SendGrid acima, isso é feito com o código myMessage.Text e myMessage.Html mostrado acima.

O código a seguir mostra como enviar emails usando a classe MailMessage em que message.Body retorna apenas o link.

void sendMail(Message message)
{
#region formatter
   string text = string.Format("Please click on this link to {0}: {1}", message.Subject, message.Body);
   string html = "Please confirm your account by clicking this link: <a href=\"" + message.Body + "\">link</a><br/>";

   html += HttpUtility.HtmlEncode(@"Or click on the copy the following link on the browser:" + message.Body);
#endregion

   MailMessage msg = new MailMessage();
   msg.From = new MailAddress("joe@contoso.com");
   msg.To.Add(new MailAddress(message.Destination));
   msg.Subject = message.Subject;
   msg.AlternateViews.Add(AlternateView.CreateAlternateViewFromString(text, null, MediaTypeNames.Text.Plain));
   msg.AlternateViews.Add(AlternateView.CreateAlternateViewFromString(html, null, MediaTypeNames.Text.Html));

   SmtpClient smtpClient = new SmtpClient("smtp.gmail.com", Convert.ToInt32(587));
   System.Net.NetworkCredential credentials = new System.Net.NetworkCredential("joe@contoso.com", "XXXXXX");
   smtpClient.Credentials = credentials;
   smtpClient.EnableSsl = true;
   smtpClient.Send(msg);
}

Aviso

Segurança – Nunca armazene dados confidenciais no código-fonte. A conta e as credenciais são armazenadas no appSetting. No Azure, você pode armazenar com segurança esses valores na guia Configurar no portal do Azure. Consulte Práticas recomendadas para implantar senhas e dados confidenciais em ASP.NET e Azure.

Insira suas credenciais do SendGrid, execute o aplicativo, registre-se com um alias de email pode selecionar o link de confirmação em seu email. Para ver como fazer isso com sua conta de email Outlook.com consulte as postagens Configuração C# SMTP para Outlook.Com SMTP Host eASP.NET Identity 2.0: configurando validação de conta e autorização bifatorial de John Atten.

Depois que um usuário seleciona o botão Registrar um email de confirmação que contém um token de validação é enviado para seu endereço de email.

Imagem da janela de confirmação enviada por email

O usuário recebe um email com um token de confirmação para sua conta.

Imagem de email recebida

Examinar o código

O código a seguir mostra o método POST ForgotPassword.

public async Task<ActionResult> ForgotPassword(ForgotPasswordViewModel model)
{
    if (ModelState.IsValid)
    {
        var user = await UserManager.FindByNameAsync(model.Email);
        if (user == null || !(await UserManager.IsEmailConfirmedAsync(user.Id)))
        {
            // Don't reveal that the user does not exist or is not confirmed
            return View("ForgotPasswordConfirmation");
        }

        var code = await UserManager.GeneratePasswordResetTokenAsync(user.Id);
        var callbackUrl = Url.Action("ResetPassword", "Account", 
    new { UserId = user.Id, code = code }, protocol: Request.Url.Scheme);
        await UserManager.SendEmailAsync(user.Id, "Reset Password", 
    "Please reset your password by clicking here: <a href=\"" + callbackUrl + "\">link</a>");        
        return View("ForgotPasswordConfirmation");
    }

    // If we got this far, something failed, redisplay form
    return View(model);
}

O método falhará silenciosamente se o email do usuário não tiver sido confirmado. Se um erro tiver sido postado para um endereço de email inválido, usuários mal-intencionados poderão usar tais informações para localizar userId (aliases de email) válidos para atacar.

O código a seguir mostra o método ConfirmEmail no controlador de conta que é chamado quando o usuário seleciona o link de confirmação no email enviado a eles:

public async Task<ActionResult> ConfirmEmail(string userId, string code)
{
    if (userId == null || code == null)
    {
        return View("Error");
    }
    var result = await UserManager.ConfirmEmailAsync(userId, code);
    if (result.Succeeded)
    {
        return View("ConfirmEmail");
    }
    AddErrors(result);
    return View();
}

Depois que um token de senha esquecido for usado, ele será invalidado. A alteração de código a seguir no método Create (no arquivo App_Start\IdentityConfig.cs) define os tokens para expirar em 3 horas.

if (dataProtectionProvider != null)
 {
    manager.UserTokenProvider =
       new DataProtectorTokenProvider<ApplicationUser>
          (dataProtectionProvider.Create("ASP.NET Identity"))
          {                    
             TokenLifespan = TimeSpan.FromHours(3)
          };
 }

Com o código acima, a senha esquecida e os tokens de confirmação de email expirarão em 3 horas. O TokenLifespan padrão é um dia.

O código a seguir mostra o método de confirmação de email:

// GET: /Account/ConfirmEmail
[AllowAnonymous]
public async Task<ActionResult> ConfirmEmail(string userId, string code)
{
   if (userId == null || code == null)
   {
      return View("Error");
   }
   IdentityResult result;
   try
   {
      result = await UserManager.ConfirmEmailAsync(userId, code);
   }
   catch (InvalidOperationException ioe)
   {
      // ConfirmEmailAsync throws when the userId is not found.
      ViewBag.errorMessage = ioe.Message;
      return View("Error");
   }

   if (result.Succeeded)
   {
      return View();
   }

   // If we got this far, something failed.
   AddErrors(result);
   ViewBag.errorMessage = "ConfirmEmail failed";
   return View("Error");
}

Para tornar seu aplicativo mais seguro, o ASP.NET Identity dá suporte à autenticação de Two-Factor (2FA). Consulte ASP.NET Identity 2.0: configurando validação de conta e autorização bifatorial de John Atten. Embora você possa definir o bloqueio de conta em falhas nas tentativas de senha de login, essa abordagem torna esse login suscetível a bloqueios de DOS. Recomendamos que você use o bloqueio de conta apenas com 2FA.

Recursos adicionais