Partilhar 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 a 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. As contas sociais usam terceiros (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 confirmação enviada por email

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 endereço de email

Recuperação/redefinição de senha

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

Imagem da janela de redefinição de senha esquecida

Em breve, o usuário receberá um email com um link que permite redefinir sua senha.

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

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

Selecionar o botão Redefinir confirmará que a senha foi redefinida.

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

Criar um aplicativo Web ASP .NET

Comece instalando e executando o Visual Studio 2017.

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

  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 Servidor Explorer, 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 AspNetUsers esquema:

    Imagem mostrando o esquema A s p Net Users

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

    Imagem mostrando dados da 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 ConfigureAuth método em App_Start\Startup.Auth.cs, que configura o pipeline OWIN e inicializa ASP.NET Identity. Examine o método ConfigureAuth. Cada CreatePerOwinContext chamada registra um retorno de chamada (salvo no OwinContext) que será chamado uma vez por solicitação para criar uma instância do tipo especificado. Você pode definir um ponto de interrupção no construtor e Create no método 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 obter mais informações, consulte Gerenciamento de tempo de vida por solicitação para a classe UserManager no ASP.NET Identity.

Quando você altera seu perfil de segurança, um novo selo de segurança é gerado e armazenado no SecurityStamp campo da tabela AspNetUsers . Observe que o SecurityStamp campo é diferente do cookie de segurança. O cookie de segurança não é armazenado na AspNetUsers tabela (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 UserId, SecurityStamp informações de tempo de expiração e .

O middleware de cookie verifica o cookie em cada solicitação. O SecurityStampValidator método na Startup classe atinge o BD e verifica o carimbo de segurança periodicamente, conforme 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. Confira meu tutorial de autenticação de dois fatores para obter mais detalhes.

De acordo com os comentários no código, o método dá suporte à UseCookieAuthentication autenticação de cookie. O SecurityStamp campo e o código associado fornecem uma camada extra de segurança ao seu aplicativo, quando você altera sua senha, você será desconectado do navegador com o qual fez logon. O SecurityStampValidator.OnValidateIdentity método 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 SecurityStamp campo 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 alterar sua senha ou alterar o logon associado (como no Facebook, Google, conta 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 você saia apenas de uma instância específica do navegador.

Imagem mostrando a janela de aplicativo de exemplo de logon único

O aplicativo de exemplo de logon único mostra como 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á conectado ao local em que fez logon neste aplicativo.

O arquivo App_Start\IdentityConfig.cs contém as ApplicationUserManagerclasses e EmailServiceSmsService . As EmailService classes e SmsService implementam cada uma da IIdentityMessageService interface, 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 do SendGrid, você pode enviar emails usando SMTP e outros mecanismos.

A Startup classe também contém uma placa clichê para adicionar logons sociais (Facebook, Twitter etc.), confira meu tutorial MVC 5 App com Facebook, Twitter, LinkedIn e Google OAuth2 Sign-on para obter mais informações.

Examine a ApplicationUserManager classe , 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 hora).
  • Autenticação de dois fatores (2FA). Abordarei 2FA e SMS em outro tutorial.
  • Conectando os serviços de email e SMS. (Abordarei o SMS em outro tutorial).

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

//     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 AspNetUsers tabela, mostradas acima.

Argumentos genéricos em IUser permitem que você derive 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 realçado acima gera uma ClaimsIdentity. ASP.NET Identidade e Autenticação de Cookie OWIN são baseadas em declarações, portanto, a estrutura exige que o aplicativo gere um ClaimsIdentity para o usuário. ClaimsIdentity tem informações sobre todas as declarações para o usuário, como o nome do usuário, a idade e as funções às quais o usuário pertence. Você também pode adicionar mais declarações para o usuário nesta fase.

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

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

O aplicativo MVC 5 com Facebook, Twitter, LinkedIn e Google OAuth2 Sign-on mostra como você pode adicionar propriedades adicionais à ApplicationUser classe .

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, ele não se registrou com o email de outra pessoa). Suponha que você teve um fórum de discussão, você gostaria de impedir "bob@example.com" de se registrar como "joe@contoso.com". Sem confirmação por email, "joe@contoso.com" pode receber emails indesejados do seu aplicativo. Suponha que Bob se registrou acidentalmente como "bib@example.com" e não tivesse notado, ele não seria capaz de usar a recuperação de senha porque o aplicativo não tem seu email correto. Email confirmação 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. Geralmente, você deseja impedir que novos usuários postem dados em seu site antes que eles sejam 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. Crie um novo projeto Web ASP.NET vazio .

  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 Identity.Samples pacote instala o código com o qual trabalharemos.

  3. Defina o projeto para usar o 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 demonstração do exemplo (o ViewBag.Link código no controlador da conta. Consulte os DisplayEmail métodos de ação e ForgotPasswordConfirmation os modos de exibição 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.

Examinar o código em App_Start\IdentityConfig.cs

O exemplo mostra como criar uma conta e adicioná-la à função Administração. 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 Seed método . Esperamos ter uma ferramenta no futuro que permita criar e administrar 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 administrador para executar as funções e as páginas de administrador do usuário. Neste exemplo, a conta de administrador é criada quando o BD é propagado.

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

Aviso

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

Conforme mencionado anteriormente, a app.CreatePerOwinContext chamada na classe de inicialização adiciona retornos de chamada ao Create método do conteúdo do banco de dados do aplicativo, ao gerenciador de usuários e às classes de gerente de função. O pipeline OWIN chama o Create método 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 HTTP Post Register método é 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 GenerateEmailConfirmationTokenAsync método cria um token de confirmação seguro e o armazena no ASP.NET repositório de dados de identidade. O método Url.Action cria um link que contém o UserId token de confirmação e . 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);
      }
   }
}

Observação

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

O código a seguir mostra como enviar emails usando a classe MailMessage em message.Body que 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 em seu 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. Confira Práticas recomendadas para implantar senhas e outros dados confidenciais no ASP.NET e no Azure.

Insira suas credenciais do SendGrid, execute o aplicativo, registre-se com um alias de email e selecione o link confirmar em seu email. Para ver como fazer isso com sua conta de email Outlook.com , consulte Configuração de SMTP do C# de John Atten para Outlook.Com host SMTP e suaidentidade de ASP.NET 2.0: configurando postagens de validação de conta e autorização de Two-Factor .

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 recebido

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, os usuários mal-intencionados poderão usar essas informações para localizar userId válido (aliases de email) para atacar.

O código a seguir mostra o ConfirmEmail método 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 Create seguir no método (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 padrão TokenLifespan é 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 a validação da conta e Two-Factor autorização de John Atten. Embora você possa definir o bloqueio de conta em falhas de tentativa de senha de logon, essa abordagem torna seu logon suscetível a bloqueios dos DOS . Recomendamos que você use o bloqueio de conta somente com 2FA.

Recursos adicionais