Language

Account Confirmation and Password Recovery with ASP.NET Identity (C#)

By Rick Anderson, Pranav Rastogi, Suhas Joshi and Hao Kung|

This tutorial will show you how to set up email for local account confirmation and allow users to reset their forgotten password in ASP.NET Identity. This article was written by Rick Anderson (@RickAndMSFT ), Pranav Rastogi (@rustd), Hao Kung, and  Suhas Joshi. The NuGet sample was written primarily by Hao Kung.

A local user account requires the user to create a password for the account, and that password is stored (securely) in the web app. ASP.NET Identity also supports social accounts, which don't require the user to create a password for the app. Social accounts use a third party (such as Google, Twitter, Facebook or Microsoft) to authenticate users. This topic covers the following:

New users register their email alias, which creates a local account.

Clicking the Register button sends a confirmation email containing a validation token to their email address.

The user is sent an email with a confirmation token for their account.

Clicking the link confirms the account.

Password recovery/reset.

Local users who forget their password can have a security token sent to their email account, enabling them to reset their password.



The user will soon get an email with a link allowing them to reset their password.


Clicking the link will take them to the Reset page.



Clicking the Reset button will confirm the password has been reset.



Create an ASP.NET  Web app

Start by installing and running Visual Studio Express 2013 for Web or Visual Studio 2013.  Install Visual Studio 2013 Update 2 or higher.

Warning: You must install Visual Studio 2013 Update 2 to complete this tutorial.
  1. Create a new ASP.NET Web project and select the MVC template. Web Forms also supports ASP.NET Identity, so you could follow similar steps in a web forms app.
  2. Leave the default authentication as Individual User Accounts.
  3. Run the app, click the Register link and register a user. At this  point, the only validation on the email is with the [EmailAddress] attribute.
  4. In Server Explorer, navigate to Data Connections\DefaultConnection\Tables\AspNetUsers, right click and select Open table definition.
  5. The following image shows the AspNetUsers schema:

  6. Right click on the AspNetUsers table and select Show Table Data.



    At this point the email has not been confirmed.

The default data store for ASP.NET Identity is Entity Framework, but you can configure it to use other data stores and to add additional fields. See Additional Resources section at the end of this tutorial.

The OWIN startup class ( Startup.cs ) is called when the app starts and invokes the ConfigureAuth method in App_Start\Startup.Auth.cs, which configures the OWIN pipeline and initializes ASP.NET Identity. Examine the ConfigureAuth method. Each CreatePerOwinContext call registers a callback (saved in the OwinContext) that will be called once per request to create an instance of the specified type. You can set a break point in the constructor and Create method of  each type (ApplicationDbContext, ApplicationUserManager) and verify they are called on each request.  A instance of  ApplicationDbContext and ApplicationUserManager is stored in the OWIN context, which can be accessed throughout the application.  ASP.NET Identity hooks into the OWIN pipeline through cookie middleware. For more information, see Per request lifetime management for UserManager class in ASP.NET Identity.

When you change your security profile, a new security stamp is generated and stored in the SecurityStamp field of the AspNetUsers table. Note, the SecurityStamp field is different from the security cookie. The security cookie is not stored in the AspNetUsers table (or anywhere else in the Identity DB). The security cookie token is self-signed using DPAPI and is created with the UserId, SecurityStamp and expiration time information.

The cookie middleware checks the cookie on each request. The SecurityStampValidator method in the Startup class hits the DB and checks security stamp periodically, as specified with the validateInterval. This only happens every 30 minutes (in our sample) unless you change your security profile. The 30 minute interval was chosen to minimize trips to the database.  See my two-factor authentication tutorial for more details.

Per the comments in the code, the UseCookieAuthentication method supports cookie authentication. The SecurityStamp field and associated code provides an extra layer of security to your app, when you change your password, you will be logged out of the browser you logged in with.  The SecurityStampValidator.OnValidateIdentity method enables the app to validate the security token when the user logs in, which is used when you change a password or use the external login. This is needed to ensure that any tokens (cookies) generated with the old password are invalidated. In the sample project, if you change the users password then a new token is generated for the user, any previous tokens are invalidated and the SecurityStamp field is updated.

The Identity system allow you to configure your app so when the users security profile changes (for example, when the user changes their password or changes associated login (such as from Facebook, Google, Microsoft account, etc.), the user is logged out of all browser instances. For example, the image below shows the Single signout sample app, which allows the user to sign out of all browser instances (in this case, IE, Firefox and Chrome) by clicking one button. Alternatively, the sample allows you to only log out of a specific browser instance.

The Single signout sample app shows how ASP.NET Identity allows you to regenerate the security token. This is needed to ensure that any tokens (cookies) generated with the old password are invalidated. This feature provides an extra layer of security to your application; when you change your password, you will be logged out where you have logged into this application.

The App_Start\IdentityConfig.cs file contains the ApplicationUserManager, EmailService and  SmsService classes.  The EmailService and  SmsService classes each implement the IIdentityMessageService interface, so you have common methods in each class to configure email and SMS. Although this tutorial only shows how to add email notification through SendGrid, you can send email using SMTP and other mechanisms.

The Startup class also contains boiler plate to add social logins (Facebook, Twitter, etc.), see  my tutorial MVC 5 App with Facebook, Twitter, LinkedIn and Google OAuth2 Sign-on for more info. 

Examine the ApplicationUserManager class, which contains the users identity information and configures the following features:

  • Password strength requirements.
  • User lock out (attempts and time).
  • Two-factor authentication (2FA). I'll cover 2FA and SMS in another tutorial.
  • Hooking up the email and SMS services. (I'll cover SMS in another tutorial).

The ApplicationUserManager class derives from the generic UserManager<ApplicationUser> class. ApplicationUser derives from  IdentityUser. IdentityUser derives from the generic IdentityUser class:

//     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; }
}

The properties above coincide with the properties in the AspNetUsers table, shown above.

Generic arguments on IUser enable you to derive a class using different types for the primary key. See the ChangePK sample which shows how to change the primary key from string to int or GUID.

ApplicationUser

 ApplicationUser  (public class ApplicationUserManager : UserManager<ApplicationUser> ) is defined in Models\IdentityModels.cs as:

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;
    }
}

The highlighted code above  generates a ClaimsIdentity. ASP.NET Identity and OWIN Cookie Authentication are claims-based, therefore the framework requires the app to generate a ClaimsIdentity for the user. ClaimsIdentity has information about all the claims for the user, such as the user's name, age and what roles the user belongs to. You can also add more claims for the user at this stage.

The OWIN  AuthenticationManager.SignIn  method passes in the ClaimsIdentity and signs in the user:

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

MVC 5 App with Facebook, Twitter, LinkedIn and Google OAuth2 Sign-on  shows how you can add additional properties to the ApplicationUser class. 

Email confirmation

It's a good idea to confirm the email a new user register with to verify they are not impersonating someone else (that is, they haven't registered with someone else's email). Suppose you had a discussion forum, you would want to prevent "bob@example.com" from registering as "joe@contoso.com". Without email confirmation, "joe@contoso.com" could get unwanted email from your app. Suppose Bob accidently registered as  "bib@example.com" and hadn't noticed it, he wouldn't be able to use password recover because the app doesn't have his correct email. Email confirmation provides only limited protection from bots and doesn't provide protection from determined spammers, they have many working email aliases they can use to register.

In the sample below, the user won't be able to change their password until their account has been confirmed (by them clicking on a confirmation link received on the email account they registered with.)  You can apply this work flow to other scenarios, for example sending a link to confirm and reset the password on new accounts created by the administrator, sending the user an email when they have changed their profile and so on. You generally want to prevent new users from posting any data to your web site before they have been confirmed by email, a SMS text message or another mechanism.

Building a more complete sample

In this section, you'll use NuGet to download a more complete sample we will work with.

  1. Create a new empty ASP.NET Web project.
  2. In the Package Manager Console,  enter the following the following commands:
    Install-Package SendGrid
    Install-Package -Prerelease Microsoft.AspNet.Identity.Samples


     In this tutorial, we'll use SendGrid to send email. The Identity.Samples package installs the code we will be working with.
  3. Set the project to use SSL.
  4. Test local account creation by running the app, clicking on the Register link, and posting the registration form.
  5. Click the demo email link, which simulates email confirmation.
  6. Remove the demo email link confirmation code from the sample (The ViewBag.Link code in the account controller. See the DisplayEmail and ForgotPasswordConfirmation action methods and razor views ).
Warning: If you change any of the security settings in this sample, productions apps will need to undergo a security audit that explicitly calls the changes made.

Examine the code in App_Start\IdentityConfig.cs

The sample shows how to create an account and add it to the Admin role.  You should replace the email in the sample with the email you will be using for the admin account. The easiest way right now to create an administrator account is programmatically in the Seed method. We hope to have a tool in the future that will allow you to create and administer users and roles. The sample code does let you create and manage users and roles, but you must first have an administrators account to run the roles and user admin pages. In this sample, the admin account is created when the DB is seeded.

Change the password and change the name to an account where you can receive email notifications.

Security Note: Never store sensitive data in your source code.

As mentioned previously, the app.CreatePerOwinContext call in the startup class adds callbacks to the Create method of the app DB content, user manager and role manger classes. The OWIN pipeline calls the  Create method on these classes for each request and stores the context for each class. The account controller exposes the user manager from the HTTP context (which contains the OWIN context):

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

When a user registers a local account, the HTTP Post Register method is called:

[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);
}

The code above uses the model data to create a new user account using the email and password entered. If the email alias is in the data store, account creation fails and the form is displayed again. The GenerateEmailConfirmationTokenAsync method creates a secure confirmation token and stores it in the ASP.NET Identity data store. The Url.Action method creates a link containing the UserId and confirmation token. This link is then emailed to the user, the user can click on the link in their email app to confirm their account.

Set up email confirmation

Go to the Azure SendGrid sign up page and register for free account.  Add code similar to the following to configure 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);
      }
   }
}
Note: Email clients frequently accept only text messages (no HTML). You should provide the message in text and HTML. In the SendGrid sample above, this is done with the myMessage.Text and myMessage.Html code shown above.

The following code shows how to send email using the MailMessage class where message.Body returns only the 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);
}
Security Note: Never store sensitive data in your source code. The account and credentials are stored in the appSetting. On Azure, you can securely store these values on the Configure tab in the Azure portal. See Jon Atten's ASP.NET MVC: Keep Private Settings Out of Source Control.

Enter your SendGrid credentials, run the app, register with an email alias can click the confirm link in your email.  To see how to do this with your Outlook.com email account, see John Atten's C# SMTP Configuration for Outlook.Com SMTP Host and his ASP.NET Identity 2.0: Setting Up Account Validation and Two-Factor Authorization posts.

Once a user clicks the Register button a confirmation email containing a validation token is sent to their email address.

The user is sent an email with a confirmation token for their account.

Examine the code

The following code shows the POST ForgotPassword method. 

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);
}

The method fails silently if the user email has not been confirmed. If an error was posted for an invalid email address, malicious users could use that information to find valid userId (email aliases) to attack.

The following code shows the ConfirmEmail method in the account controller that is called when the user clicks the confirmation link in the email sent to them:

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();
}

Once a forgotten password token has been used, it's invalidated.  The following code change in the Create method (in the  App_Start\IdentityConfig.cs file) sets the tokens to expire in 3 hours.

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

With the code above, the forgotten password and the email confirmation tokens will expire in 3 hours. The default TokenLifespan is one day.

The following code shows the email confirmation method:

// 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");
}
To make your app more secure, ASP.NET Identity supports Two-Factor authentication (2FA).  See ASP.NET Identity 2.0: Setting Up Account Validation and Two-Factor Authorization by John Atten. Although you can set account lockout on login password attempt failures, that approach makes your login susceptible to DOS lockouts. We recommend you use account lockout only with 2FA.

Additional Resources

This article was originally created on June 12, 2014

Author Information

Rick Anderson

Rick Anderson – Rick Anderson works as a programmer writer for Microsoft, focusing on ASP.NET MVC, Windows Azure and Entity Framework. You can follow him on twitter via @RickAndMSFT.

Pranav Rastogi

Pranav Rastogi – Pranav Rastogi has a Master’s degree in Computer Science from University Of Florida and he is currently working on the awesome web technologies coming out from Microsoft. He has been working with the Microsoft Web Platform for the last 4 years and has seen the light for Visual Studio 2008/2010/VS11 along with the wonderful enhancements in ASP.NET v3.5/v4.0/v4.5/WebForms/WebPages/MVC. He writes about his experiences on web development on his blog http://blogs.msdn.com/b/pranav_rastogi/ and can be followed on twitter @rustd

Suhas Joshi

Suhas Joshi – Suhas Joshi is a Software Developer Engineer in Test on the Azure Application Platform and Tools team where his focus is on ASP.NET and Web Stack Runtime.

Hao Kung

Hao Kung – Hao Kung has a Master’s Degree in Computer Science from Cornell and he has been working on the ASP.NET team for a very long time.