Confirmação de conta e recuperação de senha com ASP.NET Identity (C#)
Antes de fazer este tutorial, você deve primeiro concluir Criar um aplicativo Web MVC 5 ASP.NET seguro com login, confirmação de e-mail e redefinição de senha. Este tutorial contém mais detalhes e mostrará como configurar o e-mail para confirmação de conta local e permitir que os usuários redefina sua senha esquecida no ASP.NET Identity.
Uma conta de usuário local requer que o usuário crie uma senha para a conta e essa senha é armazenada (de forma segura) no aplicativo Web. ASP.NET Identity também suporta contas sociais, que não exigem que o usuário crie uma senha para o aplicativo. Contas sociais usam terceiros (como Google, Twitter, Facebook ou Microsoft) para autenticar utilizadores. Este tópico aborda o seguinte:
- Crie um aplicativo MVC ASP.NET e explore os recursos do ASP.NET Identity.
- Construir o exemplo de identidade
- Configurar confirmação de e-mail
Novos usuários registram seu alias de e-mail, o que cria uma conta local.
Selecionar o botão Registrar envia um e-mail de confirmação contendo um token de validação para seu endereço de e-mail.
de confirmação
O usuário recebe um e-mail com um token de confirmação para sua conta.
Selecionar o link confirma a conta.
Recuperação/redefinição de senha
Os utilizadores locais que se esquecem da palavra-passe podem ter um token de segurança enviado para a sua conta de e-mail, permitindo-lhes repor a palavra-passe.
O usuário receberá em breve um e-mail com um link que lhe permite redefinir sua senha.
Selecionar o link irá levá-los à página de redefinição.
Selecionar o botão Redefinir confirmará que a senha foi redefinida.
Criar um aplicativo Web ASP.NET
Comece instalando e executando Visual Studio 2017.
Crie um novo projeto ASP.NET Web e selecione o modelo MVC. Os Web Forms também suportam ASP.NET Identidade, pelo que pode seguir passos semelhantes numa aplicação de formulários Web.
Altere a autenticação para Contas de Usuário Individuais.
Execute o aplicativo, selecione o link Registrar e registre um usuário. Neste ponto, a única validação no e-mail é com o atributo
[EmailAddress]. 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
:Clique com o botão direito do mouse na tabela AspNetUsers e selecione Mostrar dados da tabela.
Até o momento, o e-mail 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 seção Recursos adicionais no final deste tutorial.
A classe de inicialização OWIN ( Startup.cs ) é chamada quando o aplicativo é iniciado e invoca o método ConfigureAuth
em App_Start\Startup.Auth.cs, que configura o pipeline OWIN e inicializa o ASP.NET Identity. Examine o método ConfigureAuth
. Cada chamada CreatePerOwinContext
regista um retorno de chamada (guardado no OwinContext
) que será chamado para cada solicitação para criar uma instância do tipo especificado. Você pode definir um ponto de paragem 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 integra-se no pipeline OWIN através do middleware de cookies. Para obter mais informações, consulte Gestão do ciclo de vida por pedido para a classe UserManager no ASP.NET Identity.
Quando você altera seu perfil de segurança, um novo carimbo de segurança é gerado e armazenado no 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 Identity DB). O token de cookie de segurança é autoassinado usando DPAPI e é criado com as informações de UserId, SecurityStamp
e tempo de expiração.
O middleware do cookie verifica o cookie em cada solicitação. O método SecurityStampValidator
na classe Startup
atinge o banco de dados 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 idas 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
suporta autenticação de cookies. O campo SecurityStamp
e o código associado fornecem uma camada extra de segurança ao seu aplicativo, quando você alterar sua senha, você será desconectado do navegador com o qual fez login. O método SecurityStampValidator.OnValidateIdentity
permite que o aplicativo valide o token de segurança quando o usuário faz login, que é usado quando você altera uma senha ou usa o login 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 alterar sua senha ou alterar o login associado (como do Facebook, Google, conta da Microsoft, etc.), o usuário seja desconectado de todas as instâncias do navegador. Por exemplo, a imagem abaixo mostra o aplicativo de exemplo de saída única, que permite ao utilizador sair de todas as instâncias do navegador (neste caso, IE, Firefox e Chrome) ao selecionar um botão. Como alternativa, o exemplo permite que você efetue logout apenas de uma instância específica do navegador.
O exemplo de Saída Única no aplicativo 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 a sua aplicação; quando alterar a sua palavra-passe, será desconectado de todas as sessões abertas nesta aplicação.
O arquivo App_Start\IdentityConfig.cs contém as classes ApplicationUserManager
, EmailService
e SmsService
. As classes EmailService
e SmsService
implementam a interface IIdentityMessageService
, para que você tenha métodos comuns em cada classe para configurar email e SMS. Embora este tutorial mostre apenas como adicionar notificação por e-mail por meio SendGrid, você pode enviar e-mails usando SMTP e outros mecanismos.
A classe Startup
também contém boiler plate para adicionar logins sociais (Facebook, Twitter, etc.), veja meu tutorial MVC 5 App com Facebook, Twitter, LinkedIn e Google OAuth2 Sign-on 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 da palavra-passe.
- Bloqueio do usuário (tentativas e tempo).
- Autenticação de dois fatores (2FA). Abordarei 2FA e SMS em outro tutorial.
- Conectar os serviços de e-mail e SMS. (Abordarei o 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
, mostrada acima.
Argumentos genéricos em IUser
permitem derivar uma classe usando tipos diferentes para a chave primária. Consulte o ChangePK exemplo que mostra como alterar a chave primária de string 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 no topo gera um ClaimsIdentity. ASP.NET Identity e a autenticação de cookies do 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, 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 na 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 Facebook, Twitter, LinkedIn e Google OAuth2 Sign-on mostra como você pode adicionar propriedades adicionais à classe ApplicationUser
.
Confirmação por e-mail
É uma boa ideia confirmar o e-mail com o qual um novo usuário se registra para verificar se ele não está se passando por outra pessoa (ou seja, não se registrou com o e-mail de outra pessoa). Suponha que você tenha um fórum de discussão, você gostaria de impedir que "bob@example.com"
se registre como "joe@contoso.com"
. Sem confirmação de e-mail, "joe@contoso.com"
pode receber e-mails indesejados do seu aplicativo. Suponhamos que Bob se registrou acidentalmente como "bib@example.com"
e não tivesse notado isso, ele não seria capaz de usar a recuperação de senha porque o aplicativo não tem seu e-mail correto. A confirmação de e-mail fornece apenas proteção limitada contra bots e não fornece proteção contra spammers determinados, eles têm muitos aliases de e-mail de trabalho que podem usar para se registrar. No exemplo abaixo, o usuário não poderá alterar sua senha até que sua conta tenha sido confirmada (selecionando um link de confirmação recebido na conta de e-mail 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 e-mail ao usuário quando ele tiver alterado seu perfil e assim por diante. Você geralmente quer impedir que novos usuários postem quaisquer dados em seu site antes que eles tenham sido confirmados por e-mail, uma mensagem de texto SMS ou outro mecanismo.
Crie um exemplo mais completo
Nesta seção, você usará o NuGet para baixar um exemplo mais completo com o qual trabalharemos.
Crie um novo projeto Web ASP.NET vazio .
No Console do Gerenciador de Pacotes, digite os seguintes comandos:
Install-Package SendGrid Install-Package -Prerelease Microsoft.AspNet.Identity.Samples
Neste tutorial, usaremos SendGrid para enviar e-mails. O pacote
Identity.Samples
instala o código com o qual trabalharemos.Defina o projeto para usar SSL.
Teste a criação de uma conta local executando o aplicativo, selecionando o link Registrar e publicando o formulário de registro.
Selecione o link de e-mail de demonstração, que simula a confirmação por e-mail.
Remova o código de confirmação do link de e-mail de demonstração do exemplo (O código
ViewBag.Link
no controlador da conta. Consulte os métodos de açãoDisplayEmail
eForgotPasswordConfirmation
e vistas Razor).
Advertência
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 ao papel de Admin do . Você deve substituir o e-mail no exemplo pelo e-mail que você 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 lhe permita criar e administrar utilizadores 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 administração do usuário. Neste exemplo, a conta de administrador é criada quando o banco de dados é semeado.
Altere a palavra-passe e altere o nome para uma conta onde possa receber notificações por e-mail.
Advertência
Segurança - Nunca armazene dados confidenciais no seu código-fonte.
Como mencionado anteriormente, a chamada app.CreatePerOwinContext
na classe de inicialização adiciona retornos de chamada ao método Create
do conteúdo do banco de dados do aplicativo, do gerenciador de usuários e das classes de gerenciamento 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 e-mail e a senha inseridos. Se o alias de e-mail 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 ASP.NET Identity. O método Url.Action cria um link contendo o UserId
e o token de confirmação. Este link é então enviado por e-mail para o usuário, o usuário pode selecionar no link em seu aplicativo de e-mail para confirmar sua conta.
Configurar confirmação de e-mail
Vá para 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
Os clientes de e-mail frequentemente aceitam apenas mensagens de texto (sem HTML). Você deve fornecer a mensagem em texto e HTML. No exemplo SendGrid acima, isso é feito com o código myMessage.Text
e myMessage.Html
mostrado acima.
O código a seguir mostra como enviar email usando a classe
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);
}
Advertência
Segurança - Nunca armazene dados confidenciais no seu código-fonte. A conta e as credenciais são armazenadas no appSetting. No Azure, você pode armazenar esses valores com segurança na guia Configurar
Digite suas credenciais do SendGrid, execute o aplicativo, registre-se com um alias de e-mail pode selecionar o link de confirmação em seu e-mail. Para ver como fazer isso com a sua conta de email do Outlook.com, consulte a Configuração SMTP C# de John Atten para o host SMTP do Outlook.Com e os seus artigossobre o ASP.NET Identity 2.0: Configuração da Validação de Conta e Two-Factor Autorização.
Assim que um usuário seleciona o botão Registrar, um e-mail de confirmação contendo um token de validação é enviado para seu endereço de e-mail.
O usuário recebe um e-mail com um token de confirmação para sua conta.
Examine 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 falha silenciosamente se o e-mail do usuário não tiver sido confirmado. Se um erro for publicado para um endereço de e-mail inválido, os utilizadores mal-intencionados podem usar essas informações para encontrar IDs de utilizador (alias de e-mail) 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 e-mail enviado a ele:
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();
}
Uma vez que um token de senha esquecido tenha sido usado, ele é invalidado. A seguinte alteração de código 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 e-mail expirarão em 3 horas. O TokenLifespan
padrão é um dia.
O código a seguir mostra o método de confirmação de e-mail:
// 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 a sua aplicação mais segura, o ASP.NET Identity suporta autenticação Two-Factor (2FA). Consulte ASP.NET Identity 2.0: Configuração de Validação de Conta e Two-Factor Autorização por John Atten. Embora seja possível definir o bloqueio de conta para tentativas falhadas de introdução de senha no login, essa abordagem torna o seu acesso suscetível a bloqueios de DOS. Recomendamos que você use o bloqueio de conta apenas com 2FA.
Recursos adicionais
- Visão Geral dos Provedores de Armazenamento Personalizados para ASP.NET Identity
- aplicativo MVC 5 com Facebook, Twitter, LinkedIn e Google OAuth2 Sign-on também mostra como adicionar informações de perfil à tabela de usuários.
- ASP.NET MVC e Identity 2.0: Understanding the Basics por John Atten.
- Introdução ao ASP.NET Identity
- Anunciando RTM de ASP.NET Identity 2.0.0 por Pranav Rastogi.