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:
- Crie um aplicativo MVC ASP.NET e explore ASP.NET recursos de identidade.
- Compilar o exemplo de identidade
- Configurar confirmação de email
Novos usuários registram seu alias de email, que cria uma conta local.
Selecionar o botão Registrar envia um email de confirmação contendo um token de validação para seu endereço de email.
O usuário recebe um email com um token de confirmação para sua conta.
Selecionar o link confirma a conta.
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.
Em breve, o usuário receberá um email com um link que permite redefinir sua senha.
Selecionar o link os levará para a página Redefinir.
Selecionar o botão Redefinir confirmará que a senha foi redefinida.
Criar um aplicativo Web ASP .NET
Comece instalando e executando o Visual Studio 2017.
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.
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 email é com o atributo [EmailAddress] .
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:Clique com o botão direito do mouse na tabela AspNetUsers e selecione Mostrar 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.
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 ApplicationUserManager
classes e EmailService
SmsService
. 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.
Crie um novo projeto Web ASP.NET vazio .
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.Defina o projeto para usar o SSL.
Teste a criação da conta local executando o aplicativo, selecionando o link Registrar e postando o formulário de registro.
Selecione o link de email de demonstração, que simula a confirmação de email.
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 osDisplayEmail
métodos de ação eForgotPasswordConfirmation
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.
O usuário recebe um email com um token de confirmação para sua conta.
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
- Visão geral de provedores de armazenamento personalizados para a Identidade do ASP.NET
- O 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 Identidade 2.0: Noções básicas de John Atten.
- Introdução à Identidade do ASP.NET
- Anunciando a RTM de ASP.NET Identity 2.0.0 por Pranav Rastogi.