Conferma dell'account e recupero delle password con ASP.NET Identity (C#)
Prima di eseguire questa esercitazione, è necessario completare Creare un'app Web ASP.NET MVC 5 sicura con accesso, conferma tramite posta elettronica e reimpostazione della password. Questa esercitazione contiene altri dettagli e illustra come configurare la posta elettronica per la conferma dell'account locale e consentire agli utenti di reimpostare la password dimenticata in ASP.NET Identity.
Un account utente locale richiede all'utente di creare una password per l'account e la password viene archiviata (in modo sicuro) nell'app Web. ASP.NET Identity supporta anche gli account di social networking, che non richiedono all'utente di creare una password per l'app. Gli account social utilizzano terze parti (ad esempio Google, Twitter, Facebook o Microsoft) per autenticare gli utenti. Questo argomento illustra quanto segue:
- Creare un'app ASP.NET MVC ed esplorare le funzionalità di ASP.NET Identity.
- Compilare l'esempio di identità
- Configurare la conferma tramite posta elettronica
I nuovi utenti registrano l'alias di posta elettronica, che crea un account locale.
Selezionando il pulsante Registra viene inviato un messaggio di posta elettronica di conferma contenente un token di convalida all'indirizzo di posta elettronica.
L'utente viene inviato un messaggio di posta elettronica con un token di conferma per il proprio account.
Selezionando il collegamento viene confermato l'account.
Ripristino/reimpostazione della password
Gli utenti locali che dimenticano la password possono avere un token di sicurezza inviato al proprio account di posta elettronica, consentendo loro di reimpostare la password.
L'utente riceverà presto un messaggio di posta elettronica con un collegamento che consente di reimpostare la password.
Se si seleziona il link, si accederà alla pagina di reimpostazione.
Se si seleziona il pulsante reimposta
Creare un'app Web ASP.NET
Inizia installando e eseguendo Visual Studio 2017.
Creare un nuovo progetto Web ASP.NET e selezionare il modello MVC. Web Form supporta anche ASP.NET Identity, quindi è possibile seguire passaggi simili in un'app Web Form.
Modificare l'autenticazione in account utente individuali.
Avvia l'app, seleziona il collegamento Registra e registra un utente. A questo punto, l'unica convalida sul messaggio di posta elettronica è con l'attributo [EmailAddress].
In Esplora risorse del server, passare a Connessioni dati\DefaultConnection\Tabelle\AspNetUsers, fare clic con il pulsante destro del mouse e selezionare Apri definizione tabella.
L'immagine seguente mostra lo schema
AspNetUsers
:Fare clic con il pulsante destro del mouse sulla tabella AspNetUsers e selezionare Mostra dati della tabella.
A questo punto il messaggio di posta elettronica non è stato confermato.
L'archivio dati predefinito per ASP.NET Identity è Entity Framework, ma è possibile configurarlo per l'uso di altri archivi dati e per aggiungere altri campi. Consultare la sezione Risorse aggiuntive alla fine di questa esercitazione.
La classe di avvio OWIN ( Startup.cs ) viene chiamata all'avvio dell'app e richiama il metodo ConfigureAuth
in App_Start\Startup.Auth.cs, che configura la pipeline OWIN e inizializza ASP.NET Identity. Esaminare il metodo ConfigureAuth
. Ogni chiamata CreatePerOwinContext
registra un callback (salvato nella OwinContext
) che per ogni richiesta verrà chiamato una volta per creare un'istanza del tipo specificato. Puoi impostare un punto di interruzione nel costruttore e nel metodo Create
di ogni tipo (ApplicationDbContext, ApplicationUserManager
) e verificare che vengano chiamati per ogni richiesta. Un'istanza di ApplicationDbContext
e ApplicationUserManager
viene archiviata nel contesto OWIN, accessibile in tutta l'applicazione. ASP.NET Identity si integra nella pipeline OWIN tramite il middleware dei cookie. Per ulteriori informazioni, consultare Gestione della durata delle richieste per la classe UserManager in ASP.NET Identity.
Quando si modifica il profilo di sicurezza, viene generato un nuovo indicatore di sicurezza e archiviato nel campo SecurityStamp
della tabella AspNetUsers. Si noti che il campo SecurityStamp
è diverso dal cookie di sicurezza. Il cookie di sicurezza non viene archiviato nella tabella AspNetUsers
(o in qualsiasi altro punto del database identity). Il token del cookie di sicurezza è autofirmato usando DPAPI e viene creato con le informazioni sull'ora di UserId, SecurityStamp
e di scadenza.
Il middleware dei cookie controlla il cookie per ogni richiesta. Il metodo SecurityStampValidator
nella classe Startup
raggiunge periodicamente il database e controlla periodicamente il timbro di sicurezza, come specificato con il validateInterval
. Questo avviene solo ogni 30 minuti (nell'esempio) a meno che non si modifichi il profilo di sicurezza. L'intervallo di 30 minuti è stato scelto per ridurre al minimo i viaggi al database. Per ulteriori dettagli, vedere la mia esercitazione sull'autenticazione a due fattori .
In base ai commenti nel codice, il metodo UseCookieAuthentication
supporta l'autenticazione dei cookie. Il campo SecurityStamp
e il codice associato fornisce un livello di sicurezza aggiuntivo per l'app, quando modifichi la password, sarai disconnesso dal browser con cui hai eseguito l'accesso. Il metodo SecurityStampValidator.OnValidateIdentity
consente all'app di convalidare il token di sicurezza quando l'utente accede, che viene usato quando si modifica una password o si usa l'account di accesso esterno. Ciò è necessario per garantire che tutti i token (cookie) generati con la vecchia password siano invalidati. Nel progetto di esempio, se si modifica la password degli utenti, viene generato un nuovo token per l'utente, tutti i token precedenti vengono invalidati e il campo SecurityStamp
viene aggiornato.
Il sistema di identità consente di configurare l'app in modo che quando il profilo di sicurezza degli utenti cambia (ad esempio, quando l'utente modifica la password o modifica l'account di accesso associato(ad esempio da Facebook, Google, account Microsoft e così via), l'utente viene disconnesso da tutte le istanze del browser. Ad esempio, l'immagine seguente mostra l'app di esempio "Single Signout", che consente all'utente di disconnettersi da tutte le istanze del browser (in questo caso, Internet Explorer, Firefox e Chrome) selezionando un pulsante. In alternativa, l'esempio consente di disconnettersi solo da una specifica istanza del browser.
L'esempio di single signout app illustra come ASP.NET Identity consente di rigenerare il token di sicurezza. Ciò è necessario per garantire che tutti i token (cookie) generati con la vecchia password siano invalidati. Questa funzionalità offre un ulteriore livello di sicurezza per l'applicazione; quando modifichi la password, verrai disconnesso da tutte le sessioni aperte di questa applicazione.
Il file App_Start\IdentityConfig.cs contiene le classi ApplicationUserManager
, EmailService
e SmsService
. Le classi EmailService
e SmsService
implementano l'interfaccia IIdentityMessageService
, quindi sono disponibili metodi comuni in ogni classe per configurare posta elettronica e SMS. Anche se questa esercitazione illustra solo come aggiungere notifiche tramite posta elettronica tramite SendGrid, è possibile inviare messaggi di posta elettronica usando SMTP e altri meccanismi.
La classe Startup
contiene anche codice standard per aggiungere account di accesso social (Facebook, Twitter e così via), vedi il mio tutorial sull'app MVC 5 con accesso tramite Facebook, Twitter, LinkedIn e Google OAuth2 per maggiori informazioni.
Esaminare la classe ApplicationUserManager
, che contiene le informazioni sull'identità degli utenti e configura le funzionalità seguenti:
- Requisiti di complessità della password.
- Blocco dell'utente (tentativi e tempo).
- Autenticazione a due fattori (2FA). Verranno illustrati 2FA e SMS in un'altra esercitazione.
- Collegamento dei servizi di posta elettronica e SMS. (Coprirò gli SMS in un altro tutorial).
La classe ApplicationUserManager
deriva dalla classe UserManager<ApplicationUser>
generica.
ApplicationUser
deriva da IdentityUser.
IdentityUser
deriva dalla classe IdentityUser
generica:
// 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; }
}
Le proprietà precedenti coincidono con le proprietà nella tabella AspNetUsers
, illustrate in precedenza.
Gli argomenti generici in IUser
consentono di derivare una classe usando tipi diversi per la chiave primaria. Vedere l'esempio di ChangePK che illustra come modificare la chiave primaria da stringa a int o GUID.
ApplicationUser
ApplicationUser
(public class ApplicationUserManager : UserManager<ApplicationUser>
) è definito in Models\IdentityModels.cs come:
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;
}
}
Il codice evidenziato sopra genera un ClaimsIdentity. ASP.NET Identity e l'autenticazione dei cookie di OWIN sono basate sulle attestazioni, pertanto il framework richiede che l'app generi un ClaimsIdentity
per l'utente.
ClaimsIdentity
contiene informazioni su tutte le attestazioni per l'utente, ad esempio il nome dell'utente, l'età e i ruoli a cui appartiene l'utente. È anche possibile aggiungere altre dichiarazioni per l'utente in questa fase.
Il metodo AuthenticationManager.SignIn
OWIN passa il ClaimsIdentity
e accede all'utente:
private async Task SignInAsync(ApplicationUser user, bool isPersistent)
{
AuthenticationManager.SignOut(DefaultAuthenticationTypes.ExternalCookie);
AuthenticationManager.SignIn(new AuthenticationProperties(){
IsPersistent = isPersistent },
await user.GenerateUserIdentityAsync(UserManager));
}
'app MVC 5 con Facebook, Twitter, LinkedIn e Google OAuth2 illustra come aggiungere proprietà aggiuntive alla classe ApplicationUser
.
Conferma tramite posta elettronica
È consigliabile confermare il messaggio di posta elettronica con cui un nuovo utente si registra per verificare che non stiano rappresentando un altro utente ( ovvero non sono stati registrati con un messaggio di posta elettronica di un altro utente). Si supponga di avere un forum di discussione, si vuole impedire "bob@example.com"
di registrarsi come "joe@contoso.com"
. Senza conferma tramite posta elettronica, "joe@contoso.com"
potrebbe ricevere un messaggio di posta elettronica indesiderato dall'app. Si supponga che Bob si fosse registrato accidentalmente come "bib@example.com"
e non se ne fosse accorto, non sarebbe in grado di usare il recupero password perché l'app non ha il suo indirizzo email corretto. La conferma tramite posta elettronica offre solo una protezione limitata dai bot e non fornisce protezione da spammer determinati, ma hanno molti alias di posta elettronica funzionanti che possono usare per la registrazione. Nell'esempio seguente, l'utente non sarà in grado di modificare la password finché il proprio account non è stato confermato (selezionando un collegamento di conferma ricevuto nell'account di posta elettronica con cui è stato registrato). È possibile applicare questo flusso di lavoro ad altri scenari, ad esempio inviando un collegamento per confermare e reimpostare la password nei nuovi account creati dall'amministratore, inviando all'utente un messaggio di posta elettronica quando il profilo è stato modificato e così via. In genere si vuole impedire ai nuovi utenti di pubblicare dati nel sito Web prima che siano stati confermati tramite posta elettronica, sms o un altro meccanismo.
Creare un esempio più completo
In questa sezione userai NuGet per scaricare un esempio più completo che utilizzeremo.
Creare un nuovo progetto Web ASP.NET vuoto.
Nella console di Gestione pacchetti immettere i comandi seguenti:
Install-Package SendGrid Install-Package -Prerelease Microsoft.AspNet.Identity.Samples
In questa esercitazione, useremo SendGrid per inviare email. Il pacchetto di
Identity.Samples
installa il codice con cui si lavorerà.Imposta il progetto per utilizzare SSL.
Testare la creazione dell'account locale avviando l'app, selezionando il collegamento Registra e inviando il modulo di registrazione.
Selezionare il collegamento di posta elettronica demo che simula la conferma tramite posta elettronica.
Rimuovere il codice di conferma del link email demo dall'esempio (il codice
ViewBag.Link
nel controller account. Vedere i metodi di azioneDisplayEmail
eForgotPasswordConfirmation
e le visualizzazioni Razor).
Avvertimento
Se modifichi una delle impostazioni di sicurezza in questo esempio, le applicazioni di produzione dovranno essere sottoposte a un controllo di sicurezza che elenca esplicitamente le modifiche apportate.
Esaminare il codice in App_Start\IdentityConfig.cs
L'esempio mostra come creare un account e aggiungerlo al ruolo amministratore. Sostituire il messaggio di posta elettronica nell'esempio con il messaggio di posta elettronica che verrà usato per l'account amministratore. Il modo più semplice al momento per creare un account amministratore è programmaticamente nel metodo Seed
. Ci auguriamo di avere uno strumento in futuro che consentirà di creare e amministrare utenti e ruoli. Il codice di esempio consente di creare e gestire utenti e ruoli, ma è prima necessario disporre di un account administrators per eseguire i ruoli e le pagine di amministratore utente. In questo esempio viene creato l'account amministratore quando viene eseguito il seeding del database.
Modificare la password e modificare il nome in un account in cui è possibile ricevere notifiche tramite posta elettronica.
Avvertimento
Sicurezza: non archiviare mai i dati sensibili nel codice sorgente.
Come accennato in precedenza, la chiamata app.CreatePerOwinContext
nella classe di avvio aggiunge delle callback al metodo Create
delle classi dei contenuti del database dell'app, del gestore degli utenti e del gestore dei ruoli. La pipeline OWIN chiama il metodo Create
su queste classi per ogni richiesta e archivia il contesto per ogni classe. Il controller dell'account espone il gestore degli utenti dal contesto HTTP (che contiene il contesto OWIN).
public ApplicationUserManager UserManager
{
get
{
return _userManager ??
HttpContext.GetOwinContext().GetUserManager<ApplicationUserManager>();
}
private set
{
_userManager = value;
}
}
Quando un utente registra un account locale, viene chiamato il metodo HTTP Post Register
:
[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);
}
Il codice precedente usa i dati del modello per creare un nuovo account utente usando il messaggio di posta elettronica e la password immessi. Se l'alias di posta elettronica si trova nell'archivio dati, la creazione dell'account ha esito negativo e il modulo viene visualizzato di nuovo. Il metodo GenerateEmailConfirmationTokenAsync
crea un token di conferma sicuro e lo archivia nell'archivio dati ASP.NET Identity. Il metodo Url.Action crea un collegamento contenente UserId
e il token di conferma. Questo collegamento viene quindi inviato tramite posta elettronica all'utente, l'utente può selezionare il collegamento nell'app di posta elettronica per confermare il proprio account.
Configurare la conferma tramite posta elettronica
Passare alla pagina di iscrizione di SendGrid e registrarsi per l'account gratuito. Aggiungere codice simile al seguente per configurare 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
I client di posta elettronica accettano spesso solo messaggi di testo (senza HTML). È consigliabile specificare il messaggio in testo e HTML. Nell'esempio di SendGrid precedente questa operazione viene eseguita con il codice myMessage.Text
e myMessage.Html
illustrato in precedenza.
Il codice seguente illustra come inviare messaggi di posta elettronica usando la 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);
}
Avvertimento
Sicurezza: non archiviare mai i dati sensibili nel codice sorgente. L'account e le credenziali vengono archiviati in appSetting. In Azure è possibile archiviare questi valori in modo sicuro nella scheda Configura
Immettere le credenziali di SendGrid, eseguire l'app, registrarsi con un alias di posta elettronica e selezionare il link di conferma nell'email. Per informazioni su come eseguire questa operazione con l'account di posta elettronica Outlook.com, vedere Configurazione SMTP C# di John Atten per Outlook.Com host SMTP e il relativoASP.NET Identity 2.0: Configurazione della convalida dell'account e post di autorizzazione Two-Factor.
Dopo che un utente seleziona il pulsante registra
L'utente viene inviato un messaggio di posta elettronica con un token di conferma per il proprio account.
Esaminare il codice
Il codice seguente illustra il metodo 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);
}
Il metodo ha esito negativo in modo invisibile all'utente se il messaggio di posta elettronica dell'utente non è stato confermato. Se è stato inviato un errore per un indirizzo di posta elettronica non valido, gli utenti malintenzionati potrebbero usare tali informazioni per trovare l'ID utente valido (alias di posta elettronica) da attaccare.
Il codice seguente mostra il metodo ConfirmEmail
nel controller dell'account che viene chiamato quando l'utente seleziona il link di conferma nell'email inviata.
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();
}
Una volta usato un token password dimenticato, viene invalidato. La modifica del codice seguente nel metodo di Create
(nel file App_Start\IdentityConfig.cs) imposta la scadenza dei token in 3 ore.
if (dataProtectionProvider != null)
{
manager.UserTokenProvider =
new DataProtectorTokenProvider<ApplicationUser>
(dataProtectionProvider.Create("ASP.NET Identity"))
{
TokenLifespan = TimeSpan.FromHours(3)
};
}
Con il codice precedente, la password dimenticata e i token di conferma e-mail scadranno in 3 ore. Il TokenLifespan
predefinito è un giorno.
Il codice seguente mostra il metodo di conferma tramite posta elettronica:
// 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");
}
Per rendere l'app più sicura, ASP.NET Identity supporta l'autenticazione Two-Factor (2FA). Consulta ASP.NET Identity 2.0: Impostare la convalida dell'account e Two-Factor l'autorizzazione di John Atten. Sebbene sia possibile impostare il blocco dell'account in caso di errori di tentativi di password di accesso, questo approccio rende l'account di accesso soggetto a dos blocchi. È consigliabile usare il blocco dell'account solo con 2FA.
Risorse aggiuntive
- Panoramica dei provider di archiviazione personalizzati per ASP.NET Identity
- app MVC 5 con Facebook, Twitter, LinkedIn e Google OAuth2 Sign-On illustra anche come aggiungere informazioni sul profilo alla tabella degli utenti.
- ASP.NET MVC e Identity 2.0: Comprendere i concetti di base di John Atten.
- Introduzione a ASP.NET Identity
- Annuncio del rilascio di RTM di ASP.NET Identity 2.0.0 a cura di Pranav Rastogi.