"
ASP.NET (snapshot 2017) Microsoft documentation and samples

Account confirmation and password recovery in ASP.NET Core

By Rick Anderson and Joe Audette

This tutorial shows you how to build an ASP.NET Core app with email confirmation and password reset.

Create a New ASP.NET Core Project

ASP.NET Core 2.x

This step applies to Visual Studio on Windows. See the next section for CLI instructions.

The tutorial requires Visual Studio 2017 Preview 2 or later.

New Project dialog showing “Individual User Accounts radio” selected
New Project dialog showing “Individual User Accounts radio” selected

ASP.NET Core 1.x

The tutorial requires Visual Studio 2017 or later.

New Project dialog showing “Individual User Accounts radio” selected
New Project dialog showing “Individual User Accounts radio” selected

.NET Core CLI project creation for macOS and Linux

If you’re using the CLI or SQLite, run the following in a command window:

dotnet new mvc --auth Individual

Test new user registration

Run the app, select the Register link, and register a user. Follow the instructions to run Entity Framework Core migrations. At this point, the only validation on the email is with the [EmailAddress] attribute. After you submit the registration, you are logged into the app. Later in the tutorial, we’ll change this so new users cannot log in until their email has been validated.

View the Identity database

SQL Server

Contextual menu on AspNetUsers table in SQL Server Object Explorer
Contextual menu on AspNetUsers table in SQL Server Object Explorer

Note the EmailConfirmed field is False.

You might want to use this email again in the next step when the app sends a confirmation email. Right-click on the row and select Delete. Deleting the email alias now will make it easier in the following steps.

SQLite

See (xref:)Working with SQLite in an ASP.NET Core MVC project for instructions on how to view the SQLite DB.


Require SSL and setup IIS Express for SSL

See (xref:)Enforcing SSL.

## Require email confirmation

It’s a best practice to confirm the email of a new user registration 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, and you wanted to prevent “yli@example.com” from registering as “nolivetto@contoso.com.” Without email confirmation, “nolivetto@contoso.com” could get unwanted email from your app. Suppose the user accidentally registered as “ylo@example.com” and hadn’t noticed the misspelling of “yli,” they wouldn’t be able to use password recovery because the app doesn’t have their correct email. Email confirmation provides only limited protection from bots and doesn’t provide protection from determined spammers who have many working email aliases they can use to register.

You generally want to prevent new users from posting any data to your web site before they have a confirmed email.

Update ConfigureServices to require a confirmed email:

ASP.NET Core 2.x

[!code-csharpMain]

   1:  using System;
   2:  using System.Collections.Generic;
   3:  using System.Linq;
   4:  using System.Threading.Tasks;
   5:  using Microsoft.AspNetCore.Builder;
   6:  using Microsoft.AspNetCore.Identity;
   7:  using Microsoft.EntityFrameworkCore;
   8:  using Microsoft.AspNetCore.Hosting;
   9:  using Microsoft.Extensions.Configuration;
  10:  using Microsoft.Extensions.DependencyInjection;
  11:  using WebPW.Data;
  12:  using WebPW.Models;
  13:  using WebPW.Services;
  14:   
  15:  namespace WebPW
  16:  {
  17:      public class Startup
  18:      {
  19:          public Startup(IConfiguration configuration)
  20:          {
  21:              Configuration = configuration;
  22:          }
  23:   
  24:          public IConfiguration Configuration { get; }
  25:   
  26:          // This method gets called by the runtime. Use this method to add services to the container.
  27:          #region snippet1
  28:          public void ConfigureServices(IServiceCollection services)
  29:          {
  30:              services.AddDbContext<ApplicationDbContext>(options =>
  31:                options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
  32:   
  33:              services.AddIdentity<ApplicationUser, IdentityRole>(config =>
  34:                  {
  35:                      config.SignIn.RequireConfirmedEmail = true;
  36:                  })
  37:                  .AddEntityFrameworkStores<ApplicationDbContext>()
  38:                  .AddDefaultTokenProviders();
  39:   
  40:              // Add application services.
  41:              services.AddTransient<IEmailSender, EmailSender>();
  42:   
  43:              services.AddMvc();
  44:   
  45:              services.Configure<AuthMessageSenderOptions>(Configuration);
  46:          }
  47:          #endregion
  48:   
  49:          // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
  50:          public void Configure(IApplicationBuilder app, IHostingEnvironment env)
  51:          {
  52:              if (env.IsDevelopment())
  53:              {
  54:                  app.UseDeveloperExceptionPage();
  55:                  app.UseBrowserLink();
  56:                  app.UseDatabaseErrorPage();
  57:              }
  58:              else
  59:              {
  60:                  app.UseExceptionHandler("/Home/Error");
  61:              }
  62:   
  63:              app.UseStaticFiles();
  64:   
  65:              app.UseAuthentication();
  66:   
  67:              app.UseMvc(routes =>
  68:              {
  69:                  routes.MapRoute(
  70:                      name: "default",
  71:                      template: "{controller=Home}/{action=Index}/{id?}");
  72:              });
  73:          }
  74:      }
  75:  }

ASP.NET Core 1.x

[!code-csharpMain]

   1:  using Microsoft.AspNetCore.Builder;
   2:  using Microsoft.AspNetCore.Hosting;
   3:  using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
   4:  using Microsoft.AspNetCore.Mvc;
   5:  using Microsoft.AspNetCore.Rewrite;
   6:  using Microsoft.EntityFrameworkCore;
   7:  using Microsoft.Extensions.Configuration;
   8:  using Microsoft.Extensions.DependencyInjection;
   9:  using Microsoft.Extensions.Logging;
  10:  using WebApp1.Data;
  11:  using WebApp1.Models;
  12:  using WebApp1.Services;
  13:   
  14:  namespace WebApp1
  15:  {
  16:      public class Startup
  17:      {
  18:          public Startup(IHostingEnvironment env)
  19:          {
  20:              var builder = new ConfigurationBuilder()
  21:                  .SetBasePath(env.ContentRootPath)
  22:                  .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
  23:                  .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true);
  24:   
  25:              if (env.IsDevelopment())
  26:              {
  27:                  // For more details on using the user secret store see https://go.microsoft.com/fwlink/?LinkID=532709
  28:                  builder.AddUserSecrets<Startup>();
  29:              }
  30:   
  31:              builder.AddEnvironmentVariables();
  32:              Configuration = builder.Build();
  33:          }
  34:   
  35:          public IConfigurationRoot Configuration { get; }
  36:   
  37:          // This method gets called by the runtime. Use this method to add services to the container.
  38:          // The snippets in here are used by aspnetcore/security/enforcing-ssl.md
  39:          // Any changes to this file should be checked against that doc.
  40:          #region snippet1
  41:          #region snippet2
  42:          // Requires using Microsoft.AspNetCore.Mvc;
  43:          public void ConfigureServices(IServiceCollection services)
  44:          {
  45:              services.Configure<MvcOptions>(options =>
  46:              {
  47:                  options.Filters.Add(new RequireHttpsAttribute());
  48:              });
  49:              #endregion
  50:   
  51:              // Add framework services.
  52:              services.AddDbContext<ApplicationDbContext>(options =>
  53:                  options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
  54:   
  55:              services.AddIdentity<ApplicationUser, IdentityRole>(config =>
  56:                  {
  57:                      config.SignIn.RequireConfirmedEmail = true;
  58:                  })
  59:                  .AddEntityFrameworkStores<ApplicationDbContext>()
  60:                  .AddDefaultTokenProviders();
  61:   
  62:              services.AddMvc();
  63:   
  64:              // Add application services.
  65:              services.AddTransient<IEmailSender, AuthMessageSender>();
  66:              services.AddTransient<ISmsSender, AuthMessageSender>();
  67:   
  68:              services.Configure<AuthMessageSenderOptions>(Configuration);
  69:          }
  70:          #endregion
  71:   
  72:          // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
  73:          #region snippet_AddRedirectToHttps
  74:          // Requires using Microsoft.AspNetCore.Rewrite;
  75:          public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
  76:          {
  77:              loggerFactory.AddConsole(Configuration.GetSection("Logging"));
  78:              loggerFactory.AddDebug();
  79:   
  80:              var options = new RewriteOptions()
  81:                 .AddRedirectToHttps();
  82:   
  83:              app.UseRewriter(options);
  84:              #endregion
  85:   
  86:              if (env.IsDevelopment())
  87:              {
  88:                  app.UseDeveloperExceptionPage();
  89:                  app.UseDatabaseErrorPage();
  90:                  app.UseBrowserLink();
  91:              }
  92:              else
  93:              {
  94:                  app.UseExceptionHandler("/Home/Error");
  95:              }
  96:   
  97:              app.UseStaticFiles();
  98:   
  99:              app.UseIdentity();
 100:   
 101:              // Add external authentication middleware below. To configure them please see https://go.microsoft.com/fwlink/?LinkID=532715
 102:   
 103:              app.UseMvc(routes =>
 104:              {
 105:                  routes.MapRoute(
 106:                      name: "default",
 107:                      template: "{controller=Home}/{action=Index}/{id?}");
 108:              });
 109:          }
 110:      }
 111:  }


The preceding line prevents registered users from being logged in until their email is confirmed. However, that line does not prevent new users from being logged in after they register. The default code logs in a user after they register. Once they log out, they won’t be able to log in again until they register. Later in the tutorial we’ll change the code so newly registered user are not logged in.

Configure email provider

In this tutorial, SendGrid is used to send email. You need a SendGrid account and key to send email. You can use other email providers. ASP.NET Core 2.x includes System.Net.Mail, which allows you to send email from your app. We recommend you use SendGrid or another email service to send email.

The (xref:)Options pattern is used to access the user account and key settings. For more information, see (xref:)configuration.

Create a class to fetch the secure email key. For this sample, the AuthMessageSenderOptions class is created in the Services/AuthMessageSenderOptions.cs file.

[!code-csharpMain]

   1:  namespace WebApp1.Services
   2:  {
   3:  #region snippet1
   4:      public class AuthMessageSenderOptions
   5:      {
   6:          public string SendGridUser { get; set; }
   7:          public string SendGridKey { get; set; }
   8:      }
   9:  #endregion
  10:  }

Set the SendGridUser and SendGridKey with the secret-manager tool. For example:

C:\WebAppl\src\WebApp1>dotnet user-secrets set SendGridUser RickAndMSFT
info: Successfully saved SendGridUser = RickAndMSFT to the secret store.

On Windows, Secret Manager stores your keys/value pairs in a secrets.json file in the %APPDATA%/Microsoft/UserSecrets/ directory.

The contents of the secrets.json file are not encrypted. The secrets.json file is shown below (the SendGridKey value has been removed.)

Configure startup to use AuthMessageSenderOptions

Add AuthMessageSenderOptions to the service container at the end of the ConfigureServices method in the Startup.cs file:

ASP.NET Core 2.x

[!code-csharpMain]

   1:  using System;
   2:  using System.Collections.Generic;
   3:  using System.Linq;
   4:  using System.Threading.Tasks;
   5:  using Microsoft.AspNetCore.Builder;
   6:  using Microsoft.AspNetCore.Identity;
   7:  using Microsoft.EntityFrameworkCore;
   8:  using Microsoft.AspNetCore.Hosting;
   9:  using Microsoft.Extensions.Configuration;
  10:  using Microsoft.Extensions.DependencyInjection;
  11:  using WebPW.Data;
  12:  using WebPW.Models;
  13:  using WebPW.Services;
  14:   
  15:  namespace WebPW
  16:  {
  17:      public class Startup
  18:      {
  19:          public Startup(IConfiguration configuration)
  20:          {
  21:              Configuration = configuration;
  22:          }
  23:   
  24:          public IConfiguration Configuration { get; }
  25:   
  26:          // This method gets called by the runtime. Use this method to add services to the container.
  27:          #region snippet1
  28:          public void ConfigureServices(IServiceCollection services)
  29:          {
  30:              services.AddDbContext<ApplicationDbContext>(options =>
  31:                options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
  32:   
  33:              services.AddIdentity<ApplicationUser, IdentityRole>(config =>
  34:                  {
  35:                      config.SignIn.RequireConfirmedEmail = true;
  36:                  })
  37:                  .AddEntityFrameworkStores<ApplicationDbContext>()
  38:                  .AddDefaultTokenProviders();
  39:   
  40:              // Add application services.
  41:              services.AddTransient<IEmailSender, EmailSender>();
  42:   
  43:              services.AddMvc();
  44:   
  45:              services.Configure<AuthMessageSenderOptions>(Configuration);
  46:          }
  47:          #endregion
  48:   
  49:          // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
  50:          public void Configure(IApplicationBuilder app, IHostingEnvironment env)
  51:          {
  52:              if (env.IsDevelopment())
  53:              {
  54:                  app.UseDeveloperExceptionPage();
  55:                  app.UseBrowserLink();
  56:                  app.UseDatabaseErrorPage();
  57:              }
  58:              else
  59:              {
  60:                  app.UseExceptionHandler("/Home/Error");
  61:              }
  62:   
  63:              app.UseStaticFiles();
  64:   
  65:              app.UseAuthentication();
  66:   
  67:              app.UseMvc(routes =>
  68:              {
  69:                  routes.MapRoute(
  70:                      name: "default",
  71:                      template: "{controller=Home}/{action=Index}/{id?}");
  72:              });
  73:          }
  74:      }
  75:  }

ASP.NET Core 1.x

[!code-csharpMain]

   1:  using Microsoft.AspNetCore.Builder;
   2:  using Microsoft.AspNetCore.Hosting;
   3:  using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
   4:  using Microsoft.AspNetCore.Mvc;
   5:  using Microsoft.AspNetCore.Rewrite;
   6:  using Microsoft.EntityFrameworkCore;
   7:  using Microsoft.Extensions.Configuration;
   8:  using Microsoft.Extensions.DependencyInjection;
   9:  using Microsoft.Extensions.Logging;
  10:  using WebApp1.Data;
  11:  using WebApp1.Models;
  12:  using WebApp1.Services;
  13:   
  14:  namespace WebApp1
  15:  {
  16:      public class Startup
  17:      {
  18:          public Startup(IHostingEnvironment env)
  19:          {
  20:              var builder = new ConfigurationBuilder()
  21:                  .SetBasePath(env.ContentRootPath)
  22:                  .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
  23:                  .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true);
  24:   
  25:              if (env.IsDevelopment())
  26:              {
  27:                  // For more details on using the user secret store see https://go.microsoft.com/fwlink/?LinkID=532709
  28:                  builder.AddUserSecrets<Startup>();
  29:              }
  30:   
  31:              builder.AddEnvironmentVariables();
  32:              Configuration = builder.Build();
  33:          }
  34:   
  35:          public IConfigurationRoot Configuration { get; }
  36:   
  37:          // This method gets called by the runtime. Use this method to add services to the container.
  38:          // The snippets in here are used by aspnetcore/security/enforcing-ssl.md
  39:          // Any changes to this file should be checked against that doc.
  40:          #region snippet1
  41:          #region snippet2
  42:          // Requires using Microsoft.AspNetCore.Mvc;
  43:          public void ConfigureServices(IServiceCollection services)
  44:          {
  45:              services.Configure<MvcOptions>(options =>
  46:              {
  47:                  options.Filters.Add(new RequireHttpsAttribute());
  48:              });
  49:              #endregion
  50:   
  51:              // Add framework services.
  52:              services.AddDbContext<ApplicationDbContext>(options =>
  53:                  options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
  54:   
  55:              services.AddIdentity<ApplicationUser, IdentityRole>(config =>
  56:                  {
  57:                      config.SignIn.RequireConfirmedEmail = true;
  58:                  })
  59:                  .AddEntityFrameworkStores<ApplicationDbContext>()
  60:                  .AddDefaultTokenProviders();
  61:   
  62:              services.AddMvc();
  63:   
  64:              // Add application services.
  65:              services.AddTransient<IEmailSender, AuthMessageSender>();
  66:              services.AddTransient<ISmsSender, AuthMessageSender>();
  67:   
  68:              services.Configure<AuthMessageSenderOptions>(Configuration);
  69:          }
  70:          #endregion
  71:   
  72:          // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
  73:          #region snippet_AddRedirectToHttps
  74:          // Requires using Microsoft.AspNetCore.Rewrite;
  75:          public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
  76:          {
  77:              loggerFactory.AddConsole(Configuration.GetSection("Logging"));
  78:              loggerFactory.AddDebug();
  79:   
  80:              var options = new RewriteOptions()
  81:                 .AddRedirectToHttps();
  82:   
  83:              app.UseRewriter(options);
  84:              #endregion
  85:   
  86:              if (env.IsDevelopment())
  87:              {
  88:                  app.UseDeveloperExceptionPage();
  89:                  app.UseDatabaseErrorPage();
  90:                  app.UseBrowserLink();
  91:              }
  92:              else
  93:              {
  94:                  app.UseExceptionHandler("/Home/Error");
  95:              }
  96:   
  97:              app.UseStaticFiles();
  98:   
  99:              app.UseIdentity();
 100:   
 101:              // Add external authentication middleware below. To configure them please see https://go.microsoft.com/fwlink/?LinkID=532715
 102:   
 103:              app.UseMvc(routes =>
 104:              {
 105:                  routes.MapRoute(
 106:                      name: "default",
 107:                      template: "{controller=Home}/{action=Index}/{id?}");
 108:              });
 109:          }
 110:      }
 111:  }


Configure the AuthMessageSender class

This tutorial shows how to add email notifications through SendGrid, but you can send email using SMTP and other mechanisms.

Configure SendGrid

ASP.NET Core 2.x

[!code-csharpMain]

   1:  using Microsoft.Extensions.Options;
   2:  using SendGrid;
   3:  using SendGrid.Helpers.Mail;
   4:  using System.Threading.Tasks;
   5:   
   6:  namespace WebPW.Services
   7:  {
   8:      public class EmailSender : IEmailSender
   9:      {
  10:          public EmailSender(IOptions<AuthMessageSenderOptions> optionsAccessor)
  11:          {
  12:              Options = optionsAccessor.Value;
  13:          }
  14:   
  15:          public AuthMessageSenderOptions Options { get; } //set only via Secret Manager
  16:   
  17:          public Task SendEmailAsync(string email, string subject, string message)
  18:          {
  19:              return Execute(Options.SendGridKey, subject, message, email);
  20:          }
  21:   
  22:          public Task Execute(string apiKey, string subject, string message, string email)
  23:          {
  24:              var client = new SendGridClient(apiKey);
  25:              var msg = new SendGridMessage()
  26:              {
  27:                  From = new EmailAddress("Joe@contoso.com", "Joe Smith"),
  28:                  Subject = subject,
  29:                  PlainTextContent = message,
  30:                  HtmlContent = message
  31:              };
  32:              msg.AddTo(new EmailAddress(email));
  33:              return client.SendEmailAsync(msg);
  34:          }
  35:      }
  36:  }

ASP.NET Core 1.x

[!code-csharpMain]

   1:  using Microsoft.Extensions.Options;
   2:  using SendGrid;
   3:  using SendGrid.Helpers.Mail;
   4:  using System.Threading.Tasks;
   5:   
   6:  namespace WebApp1.Services
   7:  {
   8:      public class AuthMessageSender : IEmailSender, ISmsSender
   9:      {
  10:          public AuthMessageSender(IOptions<AuthMessageSenderOptions> optionsAccessor)
  11:          {
  12:              Options = optionsAccessor.Value;
  13:          }
  14:   
  15:          public AuthMessageSenderOptions Options { get; } //set only via Secret Manager
  16:          public Task SendEmailAsync(string email, string subject, string message)
  17:          {
  18:              // Plug in your email service here to send an email.
  19:              return Execute(Options.SendGridKey, subject, message, email);
  20:          }
  21:   
  22:          public Task Execute(string apiKey, string subject, string message, string email)
  23:          {
  24:              var client = new SendGridClient(apiKey);
  25:              var msg = new SendGridMessage()
  26:              {
  27:                  From = new EmailAddress("Joe@contoso.com", "Joe Smith"),
  28:                  Subject = subject,
  29:                  PlainTextContent = message,
  30:                  HtmlContent = message
  31:              };
  32:              msg.AddTo(new EmailAddress(email));
  33:              return client.SendEmailAsync(msg);
  34:          }
  35:   
  36:          public Task SendSmsAsync(string number, string message)
  37:          {
  38:              // Plug in your SMS service here to send a text message.
  39:              return Task.FromResult(0);
  40:          }
  41:      }
  42:  }


Enable account confirmation and password recovery

The template has the code for account confirmation and password recovery. Find the [HttpPost] Register method in the AccountController.cs file.

ASP.NET Core 2.x

Prevent newly registered users from being automatically logged on by commenting out the following line:

The complete method is shown with the changed line highlighted:

[!code-csharpMain]

   1:  using System;
   2:  using System.Collections.Generic;
   3:  using System.Linq;
   4:  using System.Security.Claims;
   5:  using System.Threading.Tasks;
   6:  using Microsoft.AspNetCore.Authentication;
   7:  using Microsoft.AspNetCore.Authorization;
   8:  using Microsoft.AspNetCore.Identity;
   9:  using Microsoft.AspNetCore.Mvc;
  10:  using Microsoft.AspNetCore.Mvc.ViewFeatures;
  11:  using Microsoft.AspNetCore.Mvc.Rendering;
  12:  using Microsoft.Extensions.Logging;
  13:  using Microsoft.Extensions.Options;
  14:  using WebPW.Models;
  15:  using WebPW.Models.AccountViewModels;
  16:  using WebPW.Services;
  17:   
  18:  namespace WebPW.Controllers
  19:  {
  20:      [Authorize]
  21:      [Route("[controller]/[action]")]
  22:      public class AccountController : Controller
  23:      {
  24:          private readonly UserManager<ApplicationUser> _userManager;
  25:          private readonly SignInManager<ApplicationUser> _signInManager;
  26:          private readonly IEmailSender _emailSender;
  27:          private readonly ILogger _logger;
  28:   
  29:          public AccountController(
  30:              UserManager<ApplicationUser> userManager,
  31:              SignInManager<ApplicationUser> signInManager,
  32:              IEmailSender emailSender,
  33:              ILogger<AccountController> logger)
  34:          {
  35:              _userManager = userManager;
  36:              _signInManager = signInManager;
  37:              _emailSender = emailSender;
  38:              _logger = logger;
  39:          }
  40:   
  41:          [TempData]
  42:          public string ErrorMessage { get; set; }
  43:   
  44:          [HttpGet]
  45:          [AllowAnonymous]
  46:          public async Task<IActionResult> Login(string returnUrl = null)
  47:          {
  48:              // Clear the existing external cookie to ensure a clean login process
  49:              await HttpContext.SignOutAsync(IdentityConstants.ExternalScheme);
  50:   
  51:              ViewData["ReturnUrl"] = returnUrl;
  52:              return View();
  53:          }
  54:   
  55:          [HttpPost]
  56:          [AllowAnonymous]
  57:          [ValidateAntiForgeryToken]
  58:          public async Task<IActionResult> Login(LoginViewModel model, string returnUrl = null)
  59:          {
  60:              ViewData["ReturnUrl"] = returnUrl;
  61:              if (ModelState.IsValid)
  62:              {
  63:                  // This doesn't count login failures towards account lockout
  64:                  // To enable password failures to trigger account lockout, set lockoutOnFailure: true
  65:                  var result = await _signInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, lockoutOnFailure: false);
  66:                  if (result.Succeeded)
  67:                  {
  68:                      _logger.LogInformation("User logged in.");
  69:                      return RedirectToLocal(returnUrl);
  70:                  }
  71:                  if (result.RequiresTwoFactor)
  72:                  {
  73:                      return RedirectToAction(nameof(LoginWith2fa), new { returnUrl, model.RememberMe });
  74:                  }
  75:                  if (result.IsLockedOut)
  76:                  {
  77:                      _logger.LogWarning("User account locked out.");
  78:                      return RedirectToAction(nameof(Lockout));
  79:                  }
  80:                  else
  81:                  {
  82:                      ModelState.AddModelError(string.Empty, "Invalid login attempt.");
  83:                      return View(model);
  84:                  }
  85:              }
  86:   
  87:              // If we got this far, something failed, redisplay form
  88:              return View(model);
  89:          }
  90:   
  91:          [HttpGet]
  92:          [AllowAnonymous]
  93:          public async Task<IActionResult> LoginWith2fa(bool rememberMe, string returnUrl = null)
  94:          {
  95:              // Ensure the user has gone through the username & password screen first
  96:              var user = await _signInManager.GetTwoFactorAuthenticationUserAsync();
  97:   
  98:              if (user == null)
  99:              {
 100:                  throw new ApplicationException($"Unable to load two-factor authentication user.");
 101:              }
 102:   
 103:              var model = new LoginWith2faViewModel { RememberMe = rememberMe };
 104:              ViewData["ReturnUrl"] = returnUrl;
 105:   
 106:              return View(model);
 107:          }
 108:   
 109:          [HttpPost]
 110:          [AllowAnonymous]
 111:          [ValidateAntiForgeryToken]
 112:          public async Task<IActionResult> LoginWith2fa(LoginWith2faViewModel model, bool rememberMe, string returnUrl = null)
 113:          {
 114:              if (!ModelState.IsValid)
 115:              {
 116:                  return View(model);
 117:              }
 118:   
 119:              var user = await _signInManager.GetTwoFactorAuthenticationUserAsync();
 120:              if (user == null)
 121:              {
 122:                  throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
 123:              }
 124:   
 125:              var authenticatorCode = model.TwoFactorCode.Replace(" ", string.Empty).Replace("-", string.Empty);
 126:   
 127:              var result = await _signInManager.TwoFactorAuthenticatorSignInAsync(authenticatorCode, rememberMe, model.RememberMachine);
 128:   
 129:              if (result.Succeeded)
 130:              {
 131:                  _logger.LogInformation("User with ID {UserId} logged in with 2fa.", user.Id);
 132:                  return RedirectToLocal(returnUrl);
 133:              }
 134:              else if (result.IsLockedOut)
 135:              {
 136:                  _logger.LogWarning("User with ID {UserId} account locked out.", user.Id);
 137:                  return RedirectToAction(nameof(Lockout));
 138:              }
 139:              else
 140:              {
 141:                  _logger.LogWarning("Invalid authenticator code entered for user with ID {UserId}.", user.Id);
 142:                  ModelState.AddModelError(string.Empty, "Invalid authenticator code.");
 143:                  return View();
 144:              }
 145:          }
 146:   
 147:          [HttpGet]
 148:          [AllowAnonymous]
 149:          public async Task<IActionResult> LoginWithRecoveryCode(string returnUrl = null)
 150:          {
 151:              // Ensure the user has gone through the username & password screen first
 152:              var user = await _signInManager.GetTwoFactorAuthenticationUserAsync();
 153:              if (user == null)
 154:              {
 155:                  throw new ApplicationException($"Unable to load two-factor authentication user.");
 156:              }
 157:   
 158:              ViewData["ReturnUrl"] = returnUrl;
 159:   
 160:              return View();
 161:          }
 162:   
 163:          [HttpPost]
 164:          [AllowAnonymous]
 165:          [ValidateAntiForgeryToken]
 166:          public async Task<IActionResult> LoginWithRecoveryCode(LoginWithRecoveryCodeViewModel model, string returnUrl = null)
 167:          {
 168:              if (!ModelState.IsValid)
 169:              {
 170:                  return View(model);
 171:              }
 172:   
 173:              var user = await _signInManager.GetTwoFactorAuthenticationUserAsync();
 174:              if (user == null)
 175:              {
 176:                  throw new ApplicationException($"Unable to load two-factor authentication user.");
 177:              }
 178:   
 179:              var recoveryCode = model.RecoveryCode.Replace(" ", string.Empty);
 180:   
 181:              var result = await _signInManager.TwoFactorRecoveryCodeSignInAsync(recoveryCode);
 182:   
 183:              if (result.Succeeded)
 184:              {
 185:                  _logger.LogInformation("User with ID {UserId} logged in with a recovery code.", user.Id);
 186:                  return RedirectToLocal(returnUrl);
 187:              }
 188:              if (result.IsLockedOut)
 189:              {
 190:                  _logger.LogWarning("User with ID {UserId} account locked out.", user.Id);
 191:                  return RedirectToAction(nameof(Lockout));
 192:              }
 193:              else
 194:              {
 195:                  _logger.LogWarning("Invalid recovery code entered for user with ID {UserId}", user.Id);
 196:                  ModelState.AddModelError(string.Empty, "Invalid recovery code entered.");
 197:                  return View();
 198:              }
 199:          }
 200:   
 201:          [HttpGet]
 202:          [AllowAnonymous]
 203:          public IActionResult Lockout()
 204:          {
 205:              return View();
 206:          }
 207:   
 208:          [HttpGet]
 209:          [AllowAnonymous]
 210:          public IActionResult Register(string returnUrl = null)
 211:          {
 212:              ViewData["ReturnUrl"] = returnUrl;
 213:              return View();
 214:          }
 215:   
 216:          #region snippet_Register
 217:          [HttpPost]
 218:          [AllowAnonymous]
 219:          [ValidateAntiForgeryToken]
 220:          public async Task<IActionResult> Register(RegisterViewModel model, string returnUrl = null)
 221:          {
 222:              ViewData["ReturnUrl"] = returnUrl;
 223:              if (ModelState.IsValid)
 224:              {
 225:                  var user = new ApplicationUser { UserName = model.Email, Email = model.Email };
 226:                  var result = await _userManager.CreateAsync(user, model.Password);
 227:                  if (result.Succeeded)
 228:                  {
 229:                      _logger.LogInformation("User created a new account with password.");
 230:   
 231:                      var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
 232:                      var callbackUrl = Url.EmailConfirmationLink(user.Id, code, Request.Scheme);
 233:                      await _emailSender.SendEmailConfirmationAsync(model.Email, callbackUrl);
 234:   
 235:                      // await _signInManager.SignInAsync(user, isPersistent: false);
 236:                      _logger.LogInformation("User created a new account with password.");
 237:                      return RedirectToLocal(returnUrl);
 238:                  }
 239:                  AddErrors(result);
 240:              }
 241:   
 242:              // If we got this far, something failed, redisplay form
 243:              return View(model);
 244:          }
 245:  #endregion
 246:   
 247:          [HttpPost]
 248:          [ValidateAntiForgeryToken]
 249:          public async Task<IActionResult> Logout()
 250:          {
 251:              await _signInManager.SignOutAsync();
 252:              _logger.LogInformation("User logged out.");
 253:              return RedirectToAction(nameof(HomeController.Index), "Home");
 254:          }
 255:   
 256:          [HttpPost]
 257:          [AllowAnonymous]
 258:          [ValidateAntiForgeryToken]
 259:          public IActionResult ExternalLogin(string provider, string returnUrl = null)
 260:          {
 261:              // Request a redirect to the external login provider.
 262:              var redirectUrl = Url.Action(nameof(ExternalLoginCallback), "Account", new { returnUrl });
 263:              var properties = _signInManager.ConfigureExternalAuthenticationProperties(provider, redirectUrl);
 264:              return Challenge(properties, provider);
 265:          }
 266:   
 267:          [HttpGet]
 268:          [AllowAnonymous]
 269:          public async Task<IActionResult> ExternalLoginCallback(string returnUrl = null, string remoteError = null)
 270:          {
 271:              if (remoteError != null)
 272:              {
 273:                  ErrorMessage = $"Error from external provider: {remoteError}";
 274:                  return RedirectToAction(nameof(Login));
 275:              }
 276:              var info = await _signInManager.GetExternalLoginInfoAsync();
 277:              if (info == null)
 278:              {
 279:                  return RedirectToAction(nameof(Login));
 280:              }
 281:   
 282:              // Sign in the user with this external login provider if the user already has a login.
 283:              var result = await _signInManager.ExternalLoginSignInAsync(info.LoginProvider, info.ProviderKey, isPersistent: false, bypassTwoFactor: true);
 284:              if (result.Succeeded)
 285:              {
 286:                  _logger.LogInformation("User logged in with {Name} provider.", info.LoginProvider);
 287:                  return RedirectToLocal(returnUrl);
 288:              }
 289:              if (result.IsLockedOut)
 290:              {
 291:                  return RedirectToAction(nameof(Lockout));
 292:              }
 293:              else
 294:              {
 295:                  // If the user does not have an account, then ask the user to create an account.
 296:                  ViewData["ReturnUrl"] = returnUrl;
 297:                  ViewData["LoginProvider"] = info.LoginProvider;
 298:                  var email = info.Principal.FindFirstValue(ClaimTypes.Email);
 299:                  return View("ExternalLogin", new ExternalLoginViewModel { Email = email });
 300:              }
 301:          }
 302:   
 303:          [HttpPost]
 304:          [AllowAnonymous]
 305:          [ValidateAntiForgeryToken]
 306:          public async Task<IActionResult> ExternalLoginConfirmation(ExternalLoginViewModel model, string returnUrl = null)
 307:          {
 308:              if (ModelState.IsValid)
 309:              {
 310:                  // Get the information about the user from the external login provider
 311:                  var info = await _signInManager.GetExternalLoginInfoAsync();
 312:                  if (info == null)
 313:                  {
 314:                      throw new ApplicationException("Error loading external login information during confirmation.");
 315:                  }
 316:                  var user = new ApplicationUser { UserName = model.Email, Email = model.Email };
 317:                  var result = await _userManager.CreateAsync(user);
 318:                  if (result.Succeeded)
 319:                  {
 320:                      result = await _userManager.AddLoginAsync(user, info);
 321:                      if (result.Succeeded)
 322:                      {
 323:                          await _signInManager.SignInAsync(user, isPersistent: false);
 324:                          _logger.LogInformation("User created an account using {Name} provider.", info.LoginProvider);
 325:                          return RedirectToLocal(returnUrl);
 326:                      }
 327:                  }
 328:                  AddErrors(result);
 329:              }
 330:   
 331:              ViewData["ReturnUrl"] = returnUrl;
 332:              return View(nameof(ExternalLogin), model);
 333:          }
 334:   
 335:          [HttpGet]
 336:          [AllowAnonymous]
 337:          public async Task<IActionResult> ConfirmEmail(string userId, string code)
 338:          {
 339:              if (userId == null || code == null)
 340:              {
 341:                  return RedirectToAction(nameof(HomeController.Index), "Home");
 342:              }
 343:              var user = await _userManager.FindByIdAsync(userId);
 344:              if (user == null)
 345:              {
 346:                  throw new ApplicationException($"Unable to load user with ID '{userId}'.");
 347:              }
 348:              var result = await _userManager.ConfirmEmailAsync(user, code);
 349:              return View(result.Succeeded ? "ConfirmEmail" : "Error");
 350:          }
 351:   
 352:          [HttpGet]
 353:          [AllowAnonymous]
 354:          public IActionResult ForgotPassword()
 355:          {
 356:              return View();
 357:          }
 358:   
 359:          [HttpPost]
 360:          [AllowAnonymous]
 361:          [ValidateAntiForgeryToken]
 362:          public async Task<IActionResult> ForgotPassword(ForgotPasswordViewModel model)
 363:          {
 364:              if (ModelState.IsValid)
 365:              {
 366:                  var user = await _userManager.FindByEmailAsync(model.Email);
 367:                  if (user == null || !(await _userManager.IsEmailConfirmedAsync(user)))
 368:                  {
 369:                      // Don't reveal that the user does not exist or is not confirmed
 370:                      return RedirectToAction(nameof(ForgotPasswordConfirmation));
 371:                  }
 372:   
 373:                  // For more information on how to enable account confirmation and password reset please 
 374:                  // visit https://go.microsoft.com/fwlink/?LinkID=532713
 375:                  var code = await _userManager.GeneratePasswordResetTokenAsync(user);
 376:                  var callbackUrl = Url.ResetPasswordCallbackLink(user.Id, code, Request.Scheme);
 377:                  await _emailSender.SendEmailAsync(model.Email, "Reset Password",
 378:                     $"Please reset your password by clicking here: <a href='{callbackUrl}'>link</a>");
 379:                  return RedirectToAction(nameof(ForgotPasswordConfirmation));
 380:              }
 381:   
 382:              // If we got this far, something failed, redisplay form
 383:              return View(model);
 384:          }
 385:   
 386:          [HttpGet]
 387:          [AllowAnonymous]
 388:          public IActionResult ForgotPasswordConfirmation()
 389:          {
 390:              return View();
 391:          }
 392:   
 393:          [HttpGet]
 394:          [AllowAnonymous]
 395:          public IActionResult ResetPassword(string code = null)
 396:          {
 397:              if (code == null)
 398:              {
 399:                  throw new ApplicationException("A code must be supplied for password reset.");
 400:              }
 401:              var model = new ResetPasswordViewModel { Code = code };
 402:              return View(model);
 403:          }
 404:   
 405:          [HttpPost]
 406:          [AllowAnonymous]
 407:          [ValidateAntiForgeryToken]
 408:          public async Task<IActionResult> ResetPassword(ResetPasswordViewModel model)
 409:          {
 410:              if (!ModelState.IsValid)
 411:              {
 412:                  return View(model);
 413:              }
 414:              var user = await _userManager.FindByEmailAsync(model.Email);
 415:              if (user == null)
 416:              {
 417:                  // Don't reveal that the user does not exist
 418:                  return RedirectToAction(nameof(ResetPasswordConfirmation));
 419:              }
 420:              var result = await _userManager.ResetPasswordAsync(user, model.Code, model.Password);
 421:              if (result.Succeeded)
 422:              {
 423:                  return RedirectToAction(nameof(ResetPasswordConfirmation));
 424:              }
 425:              AddErrors(result);
 426:              return View();
 427:          }
 428:   
 429:          [HttpGet]
 430:          [AllowAnonymous]
 431:          public IActionResult ResetPasswordConfirmation()
 432:          {
 433:              return View();
 434:          }
 435:   
 436:   
 437:          [HttpGet]
 438:          public IActionResult AccessDenied()
 439:          {
 440:              return View();
 441:          }
 442:   
 443:          #region Helpers
 444:   
 445:          private void AddErrors(IdentityResult result)
 446:          {
 447:              foreach (var error in result.Errors)
 448:              {
 449:                  ModelState.AddModelError(string.Empty, error.Description);
 450:              }
 451:          }
 452:   
 453:          private IActionResult RedirectToLocal(string returnUrl)
 454:          {
 455:              if (Url.IsLocalUrl(returnUrl))
 456:              {
 457:                  return Redirect(returnUrl);
 458:              }
 459:              else
 460:              {
 461:                  return RedirectToAction(nameof(HomeController.Index), "Home");
 462:              }
 463:          }
 464:   
 465:          #endregion
 466:      }
 467:  }

Note: The previous code will fail if you implement IEmailSender and send a plain text email. See this issue for more information and a workaround.

ASP.NET Core 1.x

Uncomment the code to enable account confirmation.

[!code-csharpMain]

   1:  using System;
   2:  using System.Collections.Generic;
   3:  using System.Linq;
   4:  using System.Security.Claims;
   5:  using System.Threading.Tasks;
   6:  using Microsoft.AspNetCore.Authorization;
   7:  using Microsoft.AspNetCore.Identity;
   8:  using Microsoft.AspNetCore.Mvc;
   9:  using Microsoft.AspNetCore.Mvc.Rendering;
  10:  using Microsoft.Extensions.Logging;
  11:  using Microsoft.Extensions.Options;
  12:  using WebApp1.Models;
  13:  using WebApp1.Models.AccountViewModels;
  14:  using WebApp1.Services;
  15:   
  16:  namespace WebApp1.Controllers
  17:  {
  18:      [Authorize]
  19:      public class AccountController : Controller
  20:      {
  21:          private readonly UserManager<ApplicationUser> _userManager;
  22:          private readonly SignInManager<ApplicationUser> _signInManager;
  23:          private readonly IEmailSender _emailSender;
  24:          private readonly ISmsSender _smsSender;
  25:          private readonly ILogger _logger;
  26:          private readonly string _externalCookieScheme;
  27:   
  28:          public AccountController(
  29:              UserManager<ApplicationUser> userManager,
  30:              SignInManager<ApplicationUser> signInManager,
  31:              IOptions<IdentityCookieOptions> identityCookieOptions,
  32:              IEmailSender emailSender,
  33:              ISmsSender smsSender,
  34:              ILoggerFactory loggerFactory)
  35:          {
  36:              _userManager = userManager;
  37:              _signInManager = signInManager;
  38:              _externalCookieScheme = identityCookieOptions.Value.ExternalCookieAuthenticationScheme;
  39:              _emailSender = emailSender;
  40:              _smsSender = smsSender;
  41:              _logger = loggerFactory.CreateLogger<AccountController>();
  42:          }
  43:   
  44:          //
  45:          // GET: /Account/Login
  46:          [HttpGet]
  47:          [AllowAnonymous]
  48:          public async Task<IActionResult> Login(string returnUrl = null)
  49:          {
  50:              // Clear the existing external cookie to ensure a clean login process
  51:              await HttpContext.Authentication.SignOutAsync(_externalCookieScheme);
  52:   
  53:              ViewData["ReturnUrl"] = returnUrl;
  54:              return View();
  55:          }
  56:   
  57:          #region snippet_Login
  58:          //
  59:          // POST: /Account/Login
  60:          [HttpPost]
  61:          [AllowAnonymous]
  62:          [ValidateAntiForgeryToken]
  63:          public async Task<IActionResult> Login(LoginViewModel model, string returnUrl = null)
  64:          {
  65:              ViewData["ReturnUrl"] = returnUrl;
  66:              if (ModelState.IsValid)
  67:              {
  68:                  // Require the user to have a confirmed email before they can log on.
  69:                  var user = await _userManager.FindByEmailAsync(model.Email);
  70:                  if (user != null)
  71:                  {
  72:                      if (!await _userManager.IsEmailConfirmedAsync(user))
  73:                      {
  74:                          ModelState.AddModelError(string.Empty, 
  75:                                        "You must have a confirmed email to log in.");
  76:                          return View(model);
  77:                      }
  78:                  }
  79:                  // This doesn't count login failures towards account lockout
  80:                  // To enable password failures to trigger account lockout, 
  81:                  // set lockoutOnFailure: true
  82:                  var result = await _signInManager.PasswordSignInAsync(model.Email,
  83:                      model.Password, model.RememberMe, lockoutOnFailure: false);
  84:                  if (result.Succeeded)
  85:                  {
  86:                      _logger.LogInformation(1, "User logged in.");
  87:                      return RedirectToLocal(returnUrl);
  88:                  }
  89:                  if (result.RequiresTwoFactor)
  90:                  {
  91:                      return RedirectToAction(nameof(SendCode),
  92:                          new { ReturnUrl = returnUrl, RememberMe = model.RememberMe });
  93:                  }
  94:                  if (result.IsLockedOut)
  95:                  {
  96:                      _logger.LogWarning(2, "User account locked out.");
  97:                      return View("Lockout");
  98:                  }
  99:                  else
 100:                  {
 101:                      ModelState.AddModelError(string.Empty, "Invalid login attempt.");
 102:                      return View(model);
 103:                  }
 104:              }
 105:   
 106:              // If we got this far, something failed, redisplay form
 107:              return View(model);
 108:          }
 109:          #endregion
 110:   
 111:          //
 112:          // GET: /Account/Register
 113:          [HttpGet]
 114:          [AllowAnonymous]
 115:          public IActionResult Register(string returnUrl = null)
 116:          {
 117:              ViewData["ReturnUrl"] = returnUrl;
 118:              return View();
 119:          }
 120:   
 121:          #region snippet_Register
 122:          //
 123:          // POST: /Account/Register
 124:          [HttpPost]
 125:          [AllowAnonymous]
 126:          [ValidateAntiForgeryToken]
 127:          public async Task<IActionResult> Register(RegisterViewModel model, string returnUrl = null)
 128:          {
 129:              ViewData["ReturnUrl"] = returnUrl;
 130:              if (ModelState.IsValid)
 131:              {
 132:                  var user = new ApplicationUser { UserName = model.Email, Email = model.Email };
 133:                  var result = await _userManager.CreateAsync(user, model.Password);
 134:                  if (result.Succeeded)
 135:                  {
 136:                      // Send an email with this link
 137:                      var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
 138:                      var callbackUrl = Url.Action(nameof(ConfirmEmail), "Account",
 139:                          new { userId = user.Id, code = code }, protocol: HttpContext.Request.Scheme);
 140:                      await _emailSender.SendEmailAsync(model.Email, "Confirm your account",
 141:                  $"Please confirm your account by clicking this link: <a href='{callbackUrl}'>link</a>");
 142:   
 143:                      // Comment out following line to prevent a new user automatically logged on.
 144:                      // await _signInManager.SignInAsync(user, isPersistent: false);
 145:                      _logger.LogInformation(3, "User created a new account with password.");
 146:                      return RedirectToLocal(returnUrl);
 147:                  }
 148:                  AddErrors(result);
 149:              }
 150:   
 151:              // If we got this far, something failed, redisplay form
 152:              return View(model);
 153:          }
 154:          #endregion
 155:          //
 156:          // POST: /Account/Logout
 157:          [HttpPost]
 158:          [ValidateAntiForgeryToken]
 159:          public async Task<IActionResult> Logout()
 160:          {
 161:              await _signInManager.SignOutAsync();
 162:              _logger.LogInformation(4, "User logged out.");
 163:              return RedirectToAction(nameof(HomeController.Index), "Home");
 164:          }
 165:   
 166:          //
 167:          // POST: /Account/ExternalLogin
 168:          [HttpPost]
 169:          [AllowAnonymous]
 170:          [ValidateAntiForgeryToken]
 171:          public IActionResult ExternalLogin(string provider, string returnUrl = null)
 172:          {
 173:              // Request a redirect to the external login provider.
 174:              var redirectUrl = Url.Action(nameof(ExternalLoginCallback), "Account", new { ReturnUrl = returnUrl });
 175:              var properties = _signInManager.ConfigureExternalAuthenticationProperties(provider, redirectUrl);
 176:              return Challenge(properties, provider);
 177:          }
 178:   
 179:          //
 180:          // GET: /Account/ExternalLoginCallback
 181:          [HttpGet]
 182:          [AllowAnonymous]
 183:          public async Task<IActionResult> ExternalLoginCallback(string returnUrl = null, string remoteError = null)
 184:          {
 185:              if (remoteError != null)
 186:              {
 187:                  ModelState.AddModelError(string.Empty, $"Error from external provider: {remoteError}");
 188:                  return View(nameof(Login));
 189:              }
 190:              var info = await _signInManager.GetExternalLoginInfoAsync();
 191:              if (info == null)
 192:              {
 193:                  return RedirectToAction(nameof(Login));
 194:              }
 195:   
 196:              // Sign in the user with this external login provider if the user already has a login.
 197:              var result = await _signInManager.ExternalLoginSignInAsync(info.LoginProvider, info.ProviderKey, isPersistent: false);
 198:              if (result.Succeeded)
 199:              {
 200:                  _logger.LogInformation(5, "User logged in with {Name} provider.", info.LoginProvider);
 201:                  return RedirectToLocal(returnUrl);
 202:              }
 203:              if (result.RequiresTwoFactor)
 204:              {
 205:                  return RedirectToAction(nameof(SendCode), new { ReturnUrl = returnUrl });
 206:              }
 207:              if (result.IsLockedOut)
 208:              {
 209:                  return View("Lockout");
 210:              }
 211:              else
 212:              {
 213:                  // If the user does not have an account, then ask the user to create an account.
 214:                  ViewData["ReturnUrl"] = returnUrl;
 215:                  ViewData["LoginProvider"] = info.LoginProvider;
 216:                  var email = info.Principal.FindFirstValue(ClaimTypes.Email);
 217:                  return View("ExternalLoginConfirmation", new ExternalLoginConfirmationViewModel { Email = email });
 218:              }
 219:          }
 220:   
 221:          //
 222:          // POST: /Account/ExternalLoginConfirmation
 223:          [HttpPost]
 224:          [AllowAnonymous]
 225:          [ValidateAntiForgeryToken]
 226:          public async Task<IActionResult> ExternalLoginConfirmation(ExternalLoginConfirmationViewModel model, string returnUrl = null)
 227:          {
 228:              if (ModelState.IsValid)
 229:              {
 230:                  // Get the information about the user from the external login provider
 231:                  var info = await _signInManager.GetExternalLoginInfoAsync();
 232:                  if (info == null)
 233:                  {
 234:                      return View("ExternalLoginFailure");
 235:                  }
 236:                  var user = new ApplicationUser { UserName = model.Email, Email = model.Email };
 237:                  var result = await _userManager.CreateAsync(user);
 238:                  if (result.Succeeded)
 239:                  {
 240:                      result = await _userManager.AddLoginAsync(user, info);
 241:                      if (result.Succeeded)
 242:                      {
 243:                          await _signInManager.SignInAsync(user, isPersistent: false);
 244:                          _logger.LogInformation(6, "User created an account using {Name} provider.", info.LoginProvider);
 245:                          return RedirectToLocal(returnUrl);
 246:                      }
 247:                  }
 248:                  AddErrors(result);
 249:              }
 250:   
 251:              ViewData["ReturnUrl"] = returnUrl;
 252:              return View(model);
 253:          }
 254:   
 255:          // GET: /Account/ConfirmEmail
 256:          [HttpGet]
 257:          [AllowAnonymous]
 258:          public async Task<IActionResult> ConfirmEmail(string userId, string code)
 259:          {
 260:              if (userId == null || code == null)
 261:              {
 262:                  return View("Error");
 263:              }
 264:              var user = await _userManager.FindByIdAsync(userId);
 265:              if (user == null)
 266:              {
 267:                  return View("Error");
 268:              }
 269:              var result = await _userManager.ConfirmEmailAsync(user, code);
 270:              return View(result.Succeeded ? "ConfirmEmail" : "Error");
 271:          }
 272:   
 273:          //
 274:          // GET: /Account/ForgotPassword
 275:          [HttpGet]
 276:          [AllowAnonymous]
 277:          public IActionResult ForgotPassword()
 278:          {
 279:              return View();
 280:          }
 281:   
 282:          #region snippet_ForgotPassword
 283:          //
 284:          // POST: /Account/ForgotPassword
 285:          [HttpPost]
 286:          [AllowAnonymous]
 287:          [ValidateAntiForgeryToken]
 288:          public async Task<IActionResult> ForgotPassword(ForgotPasswordViewModel model)
 289:          {
 290:              if (ModelState.IsValid)
 291:              {
 292:                  var user = await _userManager.FindByEmailAsync(model.Email);
 293:                  if (user == null || !(await _userManager.IsEmailConfirmedAsync(user)))
 294:                  {
 295:                      // Don't reveal that the user does not exist or is not confirmed.
 296:                      return View("ForgotPasswordConfirmation");
 297:                  }
 298:   
 299:                  // Send an email with this link
 300:                  var code = await _userManager.GeneratePasswordResetTokenAsync(user);
 301:                  var callbackUrl = Url.Action(nameof(ResetPassword), "Account",
 302:                      new { userId = user.Id, code = code }, protocol: HttpContext.Request.Scheme);
 303:                  await _emailSender.SendEmailAsync(model.Email, "Reset Password",
 304:                     $"Please reset your password by clicking here: <a href='{callbackUrl}'>link</a>");
 305:                  return View("ForgotPasswordConfirmation");
 306:              }
 307:   
 308:              // If we got this far, something failed, redisplay form
 309:              return View(model);
 310:          }
 311:          #endregion
 312:   
 313:          //
 314:          // GET: /Account/ForgotPasswordConfirmation
 315:          [HttpGet]
 316:          [AllowAnonymous]
 317:          public IActionResult ForgotPasswordConfirmation()
 318:          {
 319:              return View();
 320:          }
 321:   
 322:          //
 323:          // GET: /Account/ResetPassword
 324:          [HttpGet]
 325:          [AllowAnonymous]
 326:          public IActionResult ResetPassword(string code = null)
 327:          {
 328:              return code == null ? View("Error") : View();
 329:          }
 330:   
 331:          //
 332:          // POST: /Account/ResetPassword
 333:          [HttpPost]
 334:          [AllowAnonymous]
 335:          [ValidateAntiForgeryToken]
 336:          public async Task<IActionResult> ResetPassword(ResetPasswordViewModel model)
 337:          {
 338:              if (!ModelState.IsValid)
 339:              {
 340:                  return View(model);
 341:              }
 342:              var user = await _userManager.FindByEmailAsync(model.Email);
 343:              if (user == null)
 344:              {
 345:                  // Don't reveal that the user does not exist
 346:                  return RedirectToAction(nameof(AccountController.ResetPasswordConfirmation), "Account");
 347:              }
 348:              var result = await _userManager.ResetPasswordAsync(user, model.Code, model.Password);
 349:              if (result.Succeeded)
 350:              {
 351:                  return RedirectToAction(nameof(AccountController.ResetPasswordConfirmation), "Account");
 352:              }
 353:              AddErrors(result);
 354:              return View();
 355:          }
 356:   
 357:          //
 358:          // GET: /Account/ResetPasswordConfirmation
 359:          [HttpGet]
 360:          [AllowAnonymous]
 361:          public IActionResult ResetPasswordConfirmation()
 362:          {
 363:              return View();
 364:          }
 365:   
 366:          //
 367:          // GET: /Account/SendCode
 368:          [HttpGet]
 369:          [AllowAnonymous]
 370:          public async Task<ActionResult> SendCode(string returnUrl = null, bool rememberMe = false)
 371:          {
 372:              var user = await _signInManager.GetTwoFactorAuthenticationUserAsync();
 373:              if (user == null)
 374:              {
 375:                  return View("Error");
 376:              }
 377:              var userFactors = await _userManager.GetValidTwoFactorProvidersAsync(user);
 378:              var factorOptions = userFactors.Select(purpose => new SelectListItem { Text = purpose, Value = purpose }).ToList();
 379:              return View(new SendCodeViewModel { Providers = factorOptions, ReturnUrl = returnUrl, RememberMe = rememberMe });
 380:          }
 381:   
 382:          //
 383:          // POST: /Account/SendCode
 384:          [HttpPost]
 385:          [AllowAnonymous]
 386:          [ValidateAntiForgeryToken]
 387:          public async Task<IActionResult> SendCode(SendCodeViewModel model)
 388:          {
 389:              if (!ModelState.IsValid)
 390:              {
 391:                  return View();
 392:              }
 393:   
 394:              var user = await _signInManager.GetTwoFactorAuthenticationUserAsync();
 395:              if (user == null)
 396:              {
 397:                  return View("Error");
 398:              }
 399:   
 400:              // Generate the token and send it
 401:              var code = await _userManager.GenerateTwoFactorTokenAsync(user, model.SelectedProvider);
 402:              if (string.IsNullOrWhiteSpace(code))
 403:              {
 404:                  return View("Error");
 405:              }
 406:   
 407:              var message = "Your security code is: " + code;
 408:              if (model.SelectedProvider == "Email")
 409:              {
 410:                  await _emailSender.SendEmailAsync(await _userManager.GetEmailAsync(user), "Security Code", message);
 411:              }
 412:              else if (model.SelectedProvider == "Phone")
 413:              {
 414:                  await _smsSender.SendSmsAsync(await _userManager.GetPhoneNumberAsync(user), message);
 415:              }
 416:   
 417:              return RedirectToAction(nameof(VerifyCode), new { Provider = model.SelectedProvider, ReturnUrl = model.ReturnUrl, RememberMe = model.RememberMe });
 418:          }
 419:   
 420:          //
 421:          // GET: /Account/VerifyCode
 422:          [HttpGet]
 423:          [AllowAnonymous]
 424:          public async Task<IActionResult> VerifyCode(string provider, bool rememberMe, string returnUrl = null)
 425:          {
 426:              // Require that the user has already logged in via username/password or external login
 427:              var user = await _signInManager.GetTwoFactorAuthenticationUserAsync();
 428:              if (user == null)
 429:              {
 430:                  return View("Error");
 431:              }
 432:              return View(new VerifyCodeViewModel { Provider = provider, ReturnUrl = returnUrl, RememberMe = rememberMe });
 433:          }
 434:   
 435:          //
 436:          // POST: /Account/VerifyCode
 437:          [HttpPost]
 438:          [AllowAnonymous]
 439:          [ValidateAntiForgeryToken]
 440:          public async Task<IActionResult> VerifyCode(VerifyCodeViewModel model)
 441:          {
 442:              if (!ModelState.IsValid)
 443:              {
 444:                  return View(model);
 445:              }
 446:   
 447:              // The following code protects for brute force attacks against the two factor codes.
 448:              // If a user enters incorrect codes for a specified amount of time then the user account
 449:              // will be locked out for a specified amount of time.
 450:              var result = await _signInManager.TwoFactorSignInAsync(model.Provider, model.Code, model.RememberMe, model.RememberBrowser);
 451:              if (result.Succeeded)
 452:              {
 453:                  return RedirectToLocal(model.ReturnUrl);
 454:              }
 455:              if (result.IsLockedOut)
 456:              {
 457:                  _logger.LogWarning(7, "User account locked out.");
 458:                  return View("Lockout");
 459:              }
 460:              else
 461:              {
 462:                  ModelState.AddModelError(string.Empty, "Invalid code.");
 463:                  return View(model);
 464:              }
 465:          }
 466:   
 467:          //
 468:          // GET /Account/AccessDenied
 469:          [HttpGet]
 470:          public IActionResult AccessDenied()
 471:          {
 472:              return View();
 473:          }
 474:   
 475:          #region Helpers
 476:   
 477:          private void AddErrors(IdentityResult result)
 478:          {
 479:              foreach (var error in result.Errors)
 480:              {
 481:                  ModelState.AddModelError(string.Empty, error.Description);
 482:              }
 483:          }
 484:   
 485:          private IActionResult RedirectToLocal(string returnUrl)
 486:          {
 487:              if (Url.IsLocalUrl(returnUrl))
 488:              {
 489:                  return Redirect(returnUrl);
 490:              }
 491:              else
 492:              {
 493:                  return RedirectToAction(nameof(HomeController.Index), "Home");
 494:              }
 495:          }
 496:   
 497:          #endregion
 498:      }
 499:  }

Note: We’re also preventing a newly-registered user from being automatically logged on by commenting out the following line:

Enable password recovery by uncommenting the code in the ForgotPassword action in the Controllers/AccountController.cs file.

[!code-csharpMain]

   1:  using System;
   2:  using System.Collections.Generic;
   3:  using System.Linq;
   4:  using System.Security.Claims;
   5:  using System.Threading.Tasks;
   6:  using Microsoft.AspNetCore.Authorization;
   7:  using Microsoft.AspNetCore.Identity;
   8:  using Microsoft.AspNetCore.Mvc;
   9:  using Microsoft.AspNetCore.Mvc.Rendering;
  10:  using Microsoft.Extensions.Logging;
  11:  using Microsoft.Extensions.Options;
  12:  using WebApp1.Models;
  13:  using WebApp1.Models.AccountViewModels;
  14:  using WebApp1.Services;
  15:   
  16:  namespace WebApp1.Controllers
  17:  {
  18:      [Authorize]
  19:      public class AccountController : Controller
  20:      {
  21:          private readonly UserManager<ApplicationUser> _userManager;
  22:          private readonly SignInManager<ApplicationUser> _signInManager;
  23:          private readonly IEmailSender _emailSender;
  24:          private readonly ISmsSender _smsSender;
  25:          private readonly ILogger _logger;
  26:          private readonly string _externalCookieScheme;
  27:   
  28:          public AccountController(
  29:              UserManager<ApplicationUser> userManager,
  30:              SignInManager<ApplicationUser> signInManager,
  31:              IOptions<IdentityCookieOptions> identityCookieOptions,
  32:              IEmailSender emailSender,
  33:              ISmsSender smsSender,
  34:              ILoggerFactory loggerFactory)
  35:          {
  36:              _userManager = userManager;
  37:              _signInManager = signInManager;
  38:              _externalCookieScheme = identityCookieOptions.Value.ExternalCookieAuthenticationScheme;
  39:              _emailSender = emailSender;
  40:              _smsSender = smsSender;
  41:              _logger = loggerFactory.CreateLogger<AccountController>();
  42:          }
  43:   
  44:          //
  45:          // GET: /Account/Login
  46:          [HttpGet]
  47:          [AllowAnonymous]
  48:          public async Task<IActionResult> Login(string returnUrl = null)
  49:          {
  50:              // Clear the existing external cookie to ensure a clean login process
  51:              await HttpContext.Authentication.SignOutAsync(_externalCookieScheme);
  52:   
  53:              ViewData["ReturnUrl"] = returnUrl;
  54:              return View();
  55:          }
  56:   
  57:          #region snippet_Login
  58:          //
  59:          // POST: /Account/Login
  60:          [HttpPost]
  61:          [AllowAnonymous]
  62:          [ValidateAntiForgeryToken]
  63:          public async Task<IActionResult> Login(LoginViewModel model, string returnUrl = null)
  64:          {
  65:              ViewData["ReturnUrl"] = returnUrl;
  66:              if (ModelState.IsValid)
  67:              {
  68:                  // Require the user to have a confirmed email before they can log on.
  69:                  var user = await _userManager.FindByEmailAsync(model.Email);
  70:                  if (user != null)
  71:                  {
  72:                      if (!await _userManager.IsEmailConfirmedAsync(user))
  73:                      {
  74:                          ModelState.AddModelError(string.Empty, 
  75:                                        "You must have a confirmed email to log in.");
  76:                          return View(model);
  77:                      }
  78:                  }
  79:                  // This doesn't count login failures towards account lockout
  80:                  // To enable password failures to trigger account lockout, 
  81:                  // set lockoutOnFailure: true
  82:                  var result = await _signInManager.PasswordSignInAsync(model.Email,
  83:                      model.Password, model.RememberMe, lockoutOnFailure: false);
  84:                  if (result.Succeeded)
  85:                  {
  86:                      _logger.LogInformation(1, "User logged in.");
  87:                      return RedirectToLocal(returnUrl);
  88:                  }
  89:                  if (result.RequiresTwoFactor)
  90:                  {
  91:                      return RedirectToAction(nameof(SendCode),
  92:                          new { ReturnUrl = returnUrl, RememberMe = model.RememberMe });
  93:                  }
  94:                  if (result.IsLockedOut)
  95:                  {
  96:                      _logger.LogWarning(2, "User account locked out.");
  97:                      return View("Lockout");
  98:                  }
  99:                  else
 100:                  {
 101:                      ModelState.AddModelError(string.Empty, "Invalid login attempt.");
 102:                      return View(model);
 103:                  }
 104:              }
 105:   
 106:              // If we got this far, something failed, redisplay form
 107:              return View(model);
 108:          }
 109:          #endregion
 110:   
 111:          //
 112:          // GET: /Account/Register
 113:          [HttpGet]
 114:          [AllowAnonymous]
 115:          public IActionResult Register(string returnUrl = null)
 116:          {
 117:              ViewData["ReturnUrl"] = returnUrl;
 118:              return View();
 119:          }
 120:   
 121:          #region snippet_Register
 122:          //
 123:          // POST: /Account/Register
 124:          [HttpPost]
 125:          [AllowAnonymous]
 126:          [ValidateAntiForgeryToken]
 127:          public async Task<IActionResult> Register(RegisterViewModel model, string returnUrl = null)
 128:          {
 129:              ViewData["ReturnUrl"] = returnUrl;
 130:              if (ModelState.IsValid)
 131:              {
 132:                  var user = new ApplicationUser { UserName = model.Email, Email = model.Email };
 133:                  var result = await _userManager.CreateAsync(user, model.Password);
 134:                  if (result.Succeeded)
 135:                  {
 136:                      // Send an email with this link
 137:                      var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
 138:                      var callbackUrl = Url.Action(nameof(ConfirmEmail), "Account",
 139:                          new { userId = user.Id, code = code }, protocol: HttpContext.Request.Scheme);
 140:                      await _emailSender.SendEmailAsync(model.Email, "Confirm your account",
 141:                  $"Please confirm your account by clicking this link: <a href='{callbackUrl}'>link</a>");
 142:   
 143:                      // Comment out following line to prevent a new user automatically logged on.
 144:                      // await _signInManager.SignInAsync(user, isPersistent: false);
 145:                      _logger.LogInformation(3, "User created a new account with password.");
 146:                      return RedirectToLocal(returnUrl);
 147:                  }
 148:                  AddErrors(result);
 149:              }
 150:   
 151:              // If we got this far, something failed, redisplay form
 152:              return View(model);
 153:          }
 154:          #endregion
 155:          //
 156:          // POST: /Account/Logout
 157:          [HttpPost]
 158:          [ValidateAntiForgeryToken]
 159:          public async Task<IActionResult> Logout()
 160:          {
 161:              await _signInManager.SignOutAsync();
 162:              _logger.LogInformation(4, "User logged out.");
 163:              return RedirectToAction(nameof(HomeController.Index), "Home");
 164:          }
 165:   
 166:          //
 167:          // POST: /Account/ExternalLogin
 168:          [HttpPost]
 169:          [AllowAnonymous]
 170:          [ValidateAntiForgeryToken]
 171:          public IActionResult ExternalLogin(string provider, string returnUrl = null)
 172:          {
 173:              // Request a redirect to the external login provider.
 174:              var redirectUrl = Url.Action(nameof(ExternalLoginCallback), "Account", new { ReturnUrl = returnUrl });
 175:              var properties = _signInManager.ConfigureExternalAuthenticationProperties(provider, redirectUrl);
 176:              return Challenge(properties, provider);
 177:          }
 178:   
 179:          //
 180:          // GET: /Account/ExternalLoginCallback
 181:          [HttpGet]
 182:          [AllowAnonymous]
 183:          public async Task<IActionResult> ExternalLoginCallback(string returnUrl = null, string remoteError = null)
 184:          {
 185:              if (remoteError != null)
 186:              {
 187:                  ModelState.AddModelError(string.Empty, $"Error from external provider: {remoteError}");
 188:                  return View(nameof(Login));
 189:              }
 190:              var info = await _signInManager.GetExternalLoginInfoAsync();
 191:              if (info == null)
 192:              {
 193:                  return RedirectToAction(nameof(Login));
 194:              }
 195:   
 196:              // Sign in the user with this external login provider if the user already has a login.
 197:              var result = await _signInManager.ExternalLoginSignInAsync(info.LoginProvider, info.ProviderKey, isPersistent: false);
 198:              if (result.Succeeded)
 199:              {
 200:                  _logger.LogInformation(5, "User logged in with {Name} provider.", info.LoginProvider);
 201:                  return RedirectToLocal(returnUrl);
 202:              }
 203:              if (result.RequiresTwoFactor)
 204:              {
 205:                  return RedirectToAction(nameof(SendCode), new { ReturnUrl = returnUrl });
 206:              }
 207:              if (result.IsLockedOut)
 208:              {
 209:                  return View("Lockout");
 210:              }
 211:              else
 212:              {
 213:                  // If the user does not have an account, then ask the user to create an account.
 214:                  ViewData["ReturnUrl"] = returnUrl;
 215:                  ViewData["LoginProvider"] = info.LoginProvider;
 216:                  var email = info.Principal.FindFirstValue(ClaimTypes.Email);
 217:                  return View("ExternalLoginConfirmation", new ExternalLoginConfirmationViewModel { Email = email });
 218:              }
 219:          }
 220:   
 221:          //
 222:          // POST: /Account/ExternalLoginConfirmation
 223:          [HttpPost]
 224:          [AllowAnonymous]
 225:          [ValidateAntiForgeryToken]
 226:          public async Task<IActionResult> ExternalLoginConfirmation(ExternalLoginConfirmationViewModel model, string returnUrl = null)
 227:          {
 228:              if (ModelState.IsValid)
 229:              {
 230:                  // Get the information about the user from the external login provider
 231:                  var info = await _signInManager.GetExternalLoginInfoAsync();
 232:                  if (info == null)
 233:                  {
 234:                      return View("ExternalLoginFailure");
 235:                  }
 236:                  var user = new ApplicationUser { UserName = model.Email, Email = model.Email };
 237:                  var result = await _userManager.CreateAsync(user);
 238:                  if (result.Succeeded)
 239:                  {
 240:                      result = await _userManager.AddLoginAsync(user, info);
 241:                      if (result.Succeeded)
 242:                      {
 243:                          await _signInManager.SignInAsync(user, isPersistent: false);
 244:                          _logger.LogInformation(6, "User created an account using {Name} provider.", info.LoginProvider);
 245:                          return RedirectToLocal(returnUrl);
 246:                      }
 247:                  }
 248:                  AddErrors(result);
 249:              }
 250:   
 251:              ViewData["ReturnUrl"] = returnUrl;
 252:              return View(model);
 253:          }
 254:   
 255:          // GET: /Account/ConfirmEmail
 256:          [HttpGet]
 257:          [AllowAnonymous]
 258:          public async Task<IActionResult> ConfirmEmail(string userId, string code)
 259:          {
 260:              if (userId == null || code == null)
 261:              {
 262:                  return View("Error");
 263:              }
 264:              var user = await _userManager.FindByIdAsync(userId);
 265:              if (user == null)
 266:              {
 267:                  return View("Error");
 268:              }
 269:              var result = await _userManager.ConfirmEmailAsync(user, code);
 270:              return View(result.Succeeded ? "ConfirmEmail" : "Error");
 271:          }
 272:   
 273:          //
 274:          // GET: /Account/ForgotPassword
 275:          [HttpGet]
 276:          [AllowAnonymous]
 277:          public IActionResult ForgotPassword()
 278:          {
 279:              return View();
 280:          }
 281:   
 282:          #region snippet_ForgotPassword
 283:          //
 284:          // POST: /Account/ForgotPassword
 285:          [HttpPost]
 286:          [AllowAnonymous]
 287:          [ValidateAntiForgeryToken]
 288:          public async Task<IActionResult> ForgotPassword(ForgotPasswordViewModel model)
 289:          {
 290:              if (ModelState.IsValid)
 291:              {
 292:                  var user = await _userManager.FindByEmailAsync(model.Email);
 293:                  if (user == null || !(await _userManager.IsEmailConfirmedAsync(user)))
 294:                  {
 295:                      // Don't reveal that the user does not exist or is not confirmed.
 296:                      return View("ForgotPasswordConfirmation");
 297:                  }
 298:   
 299:                  // Send an email with this link
 300:                  var code = await _userManager.GeneratePasswordResetTokenAsync(user);
 301:                  var callbackUrl = Url.Action(nameof(ResetPassword), "Account",
 302:                      new { userId = user.Id, code = code }, protocol: HttpContext.Request.Scheme);
 303:                  await _emailSender.SendEmailAsync(model.Email, "Reset Password",
 304:                     $"Please reset your password by clicking here: <a href='{callbackUrl}'>link</a>");
 305:                  return View("ForgotPasswordConfirmation");
 306:              }
 307:   
 308:              // If we got this far, something failed, redisplay form
 309:              return View(model);
 310:          }
 311:          #endregion
 312:   
 313:          //
 314:          // GET: /Account/ForgotPasswordConfirmation
 315:          [HttpGet]
 316:          [AllowAnonymous]
 317:          public IActionResult ForgotPasswordConfirmation()
 318:          {
 319:              return View();
 320:          }
 321:   
 322:          //
 323:          // GET: /Account/ResetPassword
 324:          [HttpGet]
 325:          [AllowAnonymous]
 326:          public IActionResult ResetPassword(string code = null)
 327:          {
 328:              return code == null ? View("Error") : View();
 329:          }
 330:   
 331:          //
 332:          // POST: /Account/ResetPassword
 333:          [HttpPost]
 334:          [AllowAnonymous]
 335:          [ValidateAntiForgeryToken]
 336:          public async Task<IActionResult> ResetPassword(ResetPasswordViewModel model)
 337:          {
 338:              if (!ModelState.IsValid)
 339:              {
 340:                  return View(model);
 341:              }
 342:              var user = await _userManager.FindByEmailAsync(model.Email);
 343:              if (user == null)
 344:              {
 345:                  // Don't reveal that the user does not exist
 346:                  return RedirectToAction(nameof(AccountController.ResetPasswordConfirmation), "Account");
 347:              }
 348:              var result = await _userManager.ResetPasswordAsync(user, model.Code, model.Password);
 349:              if (result.Succeeded)
 350:              {
 351:                  return RedirectToAction(nameof(AccountController.ResetPasswordConfirmation), "Account");
 352:              }
 353:              AddErrors(result);
 354:              return View();
 355:          }
 356:   
 357:          //
 358:          // GET: /Account/ResetPasswordConfirmation
 359:          [HttpGet]
 360:          [AllowAnonymous]
 361:          public IActionResult ResetPasswordConfirmation()
 362:          {
 363:              return View();
 364:          }
 365:   
 366:          //
 367:          // GET: /Account/SendCode
 368:          [HttpGet]
 369:          [AllowAnonymous]
 370:          public async Task<ActionResult> SendCode(string returnUrl = null, bool rememberMe = false)
 371:          {
 372:              var user = await _signInManager.GetTwoFactorAuthenticationUserAsync();
 373:              if (user == null)
 374:              {
 375:                  return View("Error");
 376:              }
 377:              var userFactors = await _userManager.GetValidTwoFactorProvidersAsync(user);
 378:              var factorOptions = userFactors.Select(purpose => new SelectListItem { Text = purpose, Value = purpose }).ToList();
 379:              return View(new SendCodeViewModel { Providers = factorOptions, ReturnUrl = returnUrl, RememberMe = rememberMe });
 380:          }
 381:   
 382:          //
 383:          // POST: /Account/SendCode
 384:          [HttpPost]
 385:          [AllowAnonymous]
 386:          [ValidateAntiForgeryToken]
 387:          public async Task<IActionResult> SendCode(SendCodeViewModel model)
 388:          {
 389:              if (!ModelState.IsValid)
 390:              {
 391:                  return View();
 392:              }
 393:   
 394:              var user = await _signInManager.GetTwoFactorAuthenticationUserAsync();
 395:              if (user == null)
 396:              {
 397:                  return View("Error");
 398:              }
 399:   
 400:              // Generate the token and send it
 401:              var code = await _userManager.GenerateTwoFactorTokenAsync(user, model.SelectedProvider);
 402:              if (string.IsNullOrWhiteSpace(code))
 403:              {
 404:                  return View("Error");
 405:              }
 406:   
 407:              var message = "Your security code is: " + code;
 408:              if (model.SelectedProvider == "Email")
 409:              {
 410:                  await _emailSender.SendEmailAsync(await _userManager.GetEmailAsync(user), "Security Code", message);
 411:              }
 412:              else if (model.SelectedProvider == "Phone")
 413:              {
 414:                  await _smsSender.SendSmsAsync(await _userManager.GetPhoneNumberAsync(user), message);
 415:              }
 416:   
 417:              return RedirectToAction(nameof(VerifyCode), new { Provider = model.SelectedProvider, ReturnUrl = model.ReturnUrl, RememberMe = model.RememberMe });
 418:          }
 419:   
 420:          //
 421:          // GET: /Account/VerifyCode
 422:          [HttpGet]
 423:          [AllowAnonymous]
 424:          public async Task<IActionResult> VerifyCode(string provider, bool rememberMe, string returnUrl = null)
 425:          {
 426:              // Require that the user has already logged in via username/password or external login
 427:              var user = await _signInManager.GetTwoFactorAuthenticationUserAsync();
 428:              if (user == null)
 429:              {
 430:                  return View("Error");
 431:              }
 432:              return View(new VerifyCodeViewModel { Provider = provider, ReturnUrl = returnUrl, RememberMe = rememberMe });
 433:          }
 434:   
 435:          //
 436:          // POST: /Account/VerifyCode
 437:          [HttpPost]
 438:          [AllowAnonymous]
 439:          [ValidateAntiForgeryToken]
 440:          public async Task<IActionResult> VerifyCode(VerifyCodeViewModel model)
 441:          {
 442:              if (!ModelState.IsValid)
 443:              {
 444:                  return View(model);
 445:              }
 446:   
 447:              // The following code protects for brute force attacks against the two factor codes.
 448:              // If a user enters incorrect codes for a specified amount of time then the user account
 449:              // will be locked out for a specified amount of time.
 450:              var result = await _signInManager.TwoFactorSignInAsync(model.Provider, model.Code, model.RememberMe, model.RememberBrowser);
 451:              if (result.Succeeded)
 452:              {
 453:                  return RedirectToLocal(model.ReturnUrl);
 454:              }
 455:              if (result.IsLockedOut)
 456:              {
 457:                  _logger.LogWarning(7, "User account locked out.");
 458:                  return View("Lockout");
 459:              }
 460:              else
 461:              {
 462:                  ModelState.AddModelError(string.Empty, "Invalid code.");
 463:                  return View(model);
 464:              }
 465:          }
 466:   
 467:          //
 468:          // GET /Account/AccessDenied
 469:          [HttpGet]
 470:          public IActionResult AccessDenied()
 471:          {
 472:              return View();
 473:          }
 474:   
 475:          #region Helpers
 476:   
 477:          private void AddErrors(IdentityResult result)
 478:          {
 479:              foreach (var error in result.Errors)
 480:              {
 481:                  ModelState.AddModelError(string.Empty, error.Description);
 482:              }
 483:          }
 484:   
 485:          private IActionResult RedirectToLocal(string returnUrl)
 486:          {
 487:              if (Url.IsLocalUrl(returnUrl))
 488:              {
 489:                  return Redirect(returnUrl);
 490:              }
 491:              else
 492:              {
 493:                  return RedirectToAction(nameof(HomeController.Index), "Home");
 494:              }
 495:          }
 496:   
 497:          #endregion
 498:      }
 499:  }

Uncomment the form element in Views/Account/ForgotPassword.cshtml. You might want to remove the <p> For more information on how to enable reset password ... </p> element which contains a link to this article.

[!code-htmlMain]

   1:  @model ForgotPasswordViewModel
   2:  @{
   3:      ViewData["Title"] = "Forgot your password?";
   4:  }
   5:   
   6:  <h2>@ViewData["Title"]</h2>
   7:  @*<p>
   8:      For more information on how to enable reset password please see this
   9:      <a href="https://go.microsoft.com/fwlink/?LinkID=532713">article</a>.
  10:  </p>*@
  11:   
  12:  <form asp-controller="Account" asp-action="ForgotPassword" method="post" class="form-horizontal">
  13:      <h4>Enter your email.</h4>
  14:      <hr />
  15:      <div asp-validation-summary="All" class="text-danger"></div>
  16:      <div class="form-group">
  17:          <label asp-for="Email" class="col-md-2 control-label"></label>
  18:          <div class="col-md-10">
  19:              <input asp-for="Email" class="form-control" />
  20:              <span asp-validation-for="Email" class="text-danger"></span>
  21:          </div>
  22:      </div>
  23:      <div class="form-group">
  24:          <div class="col-md-offset-2 col-md-10">
  25:              <button type="submit" class="btn btn-default">Submit</button>
  26:          </div>
  27:      </div>
  28:  </form>
  29:   
  30:  @section Scripts {
  31:      @{ await Html.RenderPartialAsync("_ValidationScriptsPartial"); }
  32:  }


Register, confirm email, and reset password

Run the web app, and test the account confirmation and password recovery flow.

Web application Account Register view
Web application Account Register view

View the manage page

Select your user name in the browser: browser window with user name

You might need to expand the navbar to see user name.

navbar
navbar

ASP.NET Core 2.x

The manage page is displayed with the Profile tab selected. The Email shows a check box indicating the email has been confirmed.

manage page
manage page

ASP.NET Core 1.x

We’ll talk about this page later in the tutorial. manage page


Test password reset

Debug email

If you can’t get email working:

Note: A security best practice is to not use production secrets in test and development. If you publish the app to Azure, you can set the SendGrid secrets as application settings in the Azure Web App portal. The configuration system is setup to read keys from environment variables.

Prevent login at registration

With the current templates, once a user completes the registration form, they are logged in (authenticated). You generally want to confirm their email before logging them in. In the section below, we will modify the code to require new users have a confirmed email before they are logged in. Update the [HttpPost] Login action in the AccountController.cs file with the following highlighted changes.

[!code-csharpMain]

   1:  using System;
   2:  using System.Collections.Generic;
   3:  using System.Linq;
   4:  using System.Security.Claims;
   5:  using System.Threading.Tasks;
   6:  using Microsoft.AspNetCore.Authorization;
   7:  using Microsoft.AspNetCore.Identity;
   8:  using Microsoft.AspNetCore.Mvc;
   9:  using Microsoft.AspNetCore.Mvc.Rendering;
  10:  using Microsoft.Extensions.Logging;
  11:  using Microsoft.Extensions.Options;
  12:  using WebApp1.Models;
  13:  using WebApp1.Models.AccountViewModels;
  14:  using WebApp1.Services;
  15:   
  16:  namespace WebApp1.Controllers
  17:  {
  18:      [Authorize]
  19:      public class AccountController : Controller
  20:      {
  21:          private readonly UserManager<ApplicationUser> _userManager;
  22:          private readonly SignInManager<ApplicationUser> _signInManager;
  23:          private readonly IEmailSender _emailSender;
  24:          private readonly ISmsSender _smsSender;
  25:          private readonly ILogger _logger;
  26:          private readonly string _externalCookieScheme;
  27:   
  28:          public AccountController(
  29:              UserManager<ApplicationUser> userManager,
  30:              SignInManager<ApplicationUser> signInManager,
  31:              IOptions<IdentityCookieOptions> identityCookieOptions,
  32:              IEmailSender emailSender,
  33:              ISmsSender smsSender,
  34:              ILoggerFactory loggerFactory)
  35:          {
  36:              _userManager = userManager;
  37:              _signInManager = signInManager;
  38:              _externalCookieScheme = identityCookieOptions.Value.ExternalCookieAuthenticationScheme;
  39:              _emailSender = emailSender;
  40:              _smsSender = smsSender;
  41:              _logger = loggerFactory.CreateLogger<AccountController>();
  42:          }
  43:   
  44:          //
  45:          // GET: /Account/Login
  46:          [HttpGet]
  47:          [AllowAnonymous]
  48:          public async Task<IActionResult> Login(string returnUrl = null)
  49:          {
  50:              // Clear the existing external cookie to ensure a clean login process
  51:              await HttpContext.Authentication.SignOutAsync(_externalCookieScheme);
  52:   
  53:              ViewData["ReturnUrl"] = returnUrl;
  54:              return View();
  55:          }
  56:   
  57:          #region snippet_Login
  58:          //
  59:          // POST: /Account/Login
  60:          [HttpPost]
  61:          [AllowAnonymous]
  62:          [ValidateAntiForgeryToken]
  63:          public async Task<IActionResult> Login(LoginViewModel model, string returnUrl = null)
  64:          {
  65:              ViewData["ReturnUrl"] = returnUrl;
  66:              if (ModelState.IsValid)
  67:              {
  68:                  // Require the user to have a confirmed email before they can log on.
  69:                  var user = await _userManager.FindByEmailAsync(model.Email);
  70:                  if (user != null)
  71:                  {
  72:                      if (!await _userManager.IsEmailConfirmedAsync(user))
  73:                      {
  74:                          ModelState.AddModelError(string.Empty, 
  75:                                        "You must have a confirmed email to log in.");
  76:                          return View(model);
  77:                      }
  78:                  }
  79:                  // This doesn't count login failures towards account lockout
  80:                  // To enable password failures to trigger account lockout, 
  81:                  // set lockoutOnFailure: true
  82:                  var result = await _signInManager.PasswordSignInAsync(model.Email,
  83:                      model.Password, model.RememberMe, lockoutOnFailure: false);
  84:                  if (result.Succeeded)
  85:                  {
  86:                      _logger.LogInformation(1, "User logged in.");
  87:                      return RedirectToLocal(returnUrl);
  88:                  }
  89:                  if (result.RequiresTwoFactor)
  90:                  {
  91:                      return RedirectToAction(nameof(SendCode),
  92:                          new { ReturnUrl = returnUrl, RememberMe = model.RememberMe });
  93:                  }
  94:                  if (result.IsLockedOut)
  95:                  {
  96:                      _logger.LogWarning(2, "User account locked out.");
  97:                      return View("Lockout");
  98:                  }
  99:                  else
 100:                  {
 101:                      ModelState.AddModelError(string.Empty, "Invalid login attempt.");
 102:                      return View(model);
 103:                  }
 104:              }
 105:   
 106:              // If we got this far, something failed, redisplay form
 107:              return View(model);
 108:          }
 109:          #endregion
 110:   
 111:          //
 112:          // GET: /Account/Register
 113:          [HttpGet]
 114:          [AllowAnonymous]
 115:          public IActionResult Register(string returnUrl = null)
 116:          {
 117:              ViewData["ReturnUrl"] = returnUrl;
 118:              return View();
 119:          }
 120:   
 121:          #region snippet_Register
 122:          //
 123:          // POST: /Account/Register
 124:          [HttpPost]
 125:          [AllowAnonymous]
 126:          [ValidateAntiForgeryToken]
 127:          public async Task<IActionResult> Register(RegisterViewModel model, string returnUrl = null)
 128:          {
 129:              ViewData["ReturnUrl"] = returnUrl;
 130:              if (ModelState.IsValid)
 131:              {
 132:                  var user = new ApplicationUser { UserName = model.Email, Email = model.Email };
 133:                  var result = await _userManager.CreateAsync(user, model.Password);
 134:                  if (result.Succeeded)
 135:                  {
 136:                      // Send an email with this link
 137:                      var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
 138:                      var callbackUrl = Url.Action(nameof(ConfirmEmail), "Account",
 139:                          new { userId = user.Id, code = code }, protocol: HttpContext.Request.Scheme);
 140:                      await _emailSender.SendEmailAsync(model.Email, "Confirm your account",
 141:                  $"Please confirm your account by clicking this link: <a href='{callbackUrl}'>link</a>");
 142:   
 143:                      // Comment out following line to prevent a new user automatically logged on.
 144:                      // await _signInManager.SignInAsync(user, isPersistent: false);
 145:                      _logger.LogInformation(3, "User created a new account with password.");
 146:                      return RedirectToLocal(returnUrl);
 147:                  }
 148:                  AddErrors(result);
 149:              }
 150:   
 151:              // If we got this far, something failed, redisplay form
 152:              return View(model);
 153:          }
 154:          #endregion
 155:          //
 156:          // POST: /Account/Logout
 157:          [HttpPost]
 158:          [ValidateAntiForgeryToken]
 159:          public async Task<IActionResult> Logout()
 160:          {
 161:              await _signInManager.SignOutAsync();
 162:              _logger.LogInformation(4, "User logged out.");
 163:              return RedirectToAction(nameof(HomeController.Index), "Home");
 164:          }
 165:   
 166:          //
 167:          // POST: /Account/ExternalLogin
 168:          [HttpPost]
 169:          [AllowAnonymous]
 170:          [ValidateAntiForgeryToken]
 171:          public IActionResult ExternalLogin(string provider, string returnUrl = null)
 172:          {
 173:              // Request a redirect to the external login provider.
 174:              var redirectUrl = Url.Action(nameof(ExternalLoginCallback), "Account", new { ReturnUrl = returnUrl });
 175:              var properties = _signInManager.ConfigureExternalAuthenticationProperties(provider, redirectUrl);
 176:              return Challenge(properties, provider);
 177:          }
 178:   
 179:          //
 180:          // GET: /Account/ExternalLoginCallback
 181:          [HttpGet]
 182:          [AllowAnonymous]
 183:          public async Task<IActionResult> ExternalLoginCallback(string returnUrl = null, string remoteError = null)
 184:          {
 185:              if (remoteError != null)
 186:              {
 187:                  ModelState.AddModelError(string.Empty, $"Error from external provider: {remoteError}");
 188:                  return View(nameof(Login));
 189:              }
 190:              var info = await _signInManager.GetExternalLoginInfoAsync();
 191:              if (info == null)
 192:              {
 193:                  return RedirectToAction(nameof(Login));
 194:              }
 195:   
 196:              // Sign in the user with this external login provider if the user already has a login.
 197:              var result = await _signInManager.ExternalLoginSignInAsync(info.LoginProvider, info.ProviderKey, isPersistent: false);
 198:              if (result.Succeeded)
 199:              {
 200:                  _logger.LogInformation(5, "User logged in with {Name} provider.", info.LoginProvider);
 201:                  return RedirectToLocal(returnUrl);
 202:              }
 203:              if (result.RequiresTwoFactor)
 204:              {
 205:                  return RedirectToAction(nameof(SendCode), new { ReturnUrl = returnUrl });
 206:              }
 207:              if (result.IsLockedOut)
 208:              {
 209:                  return View("Lockout");
 210:              }
 211:              else
 212:              {
 213:                  // If the user does not have an account, then ask the user to create an account.
 214:                  ViewData["ReturnUrl"] = returnUrl;
 215:                  ViewData["LoginProvider"] = info.LoginProvider;
 216:                  var email = info.Principal.FindFirstValue(ClaimTypes.Email);
 217:                  return View("ExternalLoginConfirmation", new ExternalLoginConfirmationViewModel { Email = email });
 218:              }
 219:          }
 220:   
 221:          //
 222:          // POST: /Account/ExternalLoginConfirmation
 223:          [HttpPost]
 224:          [AllowAnonymous]
 225:          [ValidateAntiForgeryToken]
 226:          public async Task<IActionResult> ExternalLoginConfirmation(ExternalLoginConfirmationViewModel model, string returnUrl = null)
 227:          {
 228:              if (ModelState.IsValid)
 229:              {
 230:                  // Get the information about the user from the external login provider
 231:                  var info = await _signInManager.GetExternalLoginInfoAsync();
 232:                  if (info == null)
 233:                  {
 234:                      return View("ExternalLoginFailure");
 235:                  }
 236:                  var user = new ApplicationUser { UserName = model.Email, Email = model.Email };
 237:                  var result = await _userManager.CreateAsync(user);
 238:                  if (result.Succeeded)
 239:                  {
 240:                      result = await _userManager.AddLoginAsync(user, info);
 241:                      if (result.Succeeded)
 242:                      {
 243:                          await _signInManager.SignInAsync(user, isPersistent: false);
 244:                          _logger.LogInformation(6, "User created an account using {Name} provider.", info.LoginProvider);
 245:                          return RedirectToLocal(returnUrl);
 246:                      }
 247:                  }
 248:                  AddErrors(result);
 249:              }
 250:   
 251:              ViewData["ReturnUrl"] = returnUrl;
 252:              return View(model);
 253:          }
 254:   
 255:          // GET: /Account/ConfirmEmail
 256:          [HttpGet]
 257:          [AllowAnonymous]
 258:          public async Task<IActionResult> ConfirmEmail(string userId, string code)
 259:          {
 260:              if (userId == null || code == null)
 261:              {
 262:                  return View("Error");
 263:              }
 264:              var user = await _userManager.FindByIdAsync(userId);
 265:              if (user == null)
 266:              {
 267:                  return View("Error");
 268:              }
 269:              var result = await _userManager.ConfirmEmailAsync(user, code);
 270:              return View(result.Succeeded ? "ConfirmEmail" : "Error");
 271:          }
 272:   
 273:          //
 274:          // GET: /Account/ForgotPassword
 275:          [HttpGet]
 276:          [AllowAnonymous]
 277:          public IActionResult ForgotPassword()
 278:          {
 279:              return View();
 280:          }
 281:   
 282:          #region snippet_ForgotPassword
 283:          //
 284:          // POST: /Account/ForgotPassword
 285:          [HttpPost]
 286:          [AllowAnonymous]
 287:          [ValidateAntiForgeryToken]
 288:          public async Task<IActionResult> ForgotPassword(ForgotPasswordViewModel model)
 289:          {
 290:              if (ModelState.IsValid)
 291:              {
 292:                  var user = await _userManager.FindByEmailAsync(model.Email);
 293:                  if (user == null || !(await _userManager.IsEmailConfirmedAsync(user)))
 294:                  {
 295:                      // Don't reveal that the user does not exist or is not confirmed.
 296:                      return View("ForgotPasswordConfirmation");
 297:                  }
 298:   
 299:                  // Send an email with this link
 300:                  var code = await _userManager.GeneratePasswordResetTokenAsync(user);
 301:                  var callbackUrl = Url.Action(nameof(ResetPassword), "Account",
 302:                      new { userId = user.Id, code = code }, protocol: HttpContext.Request.Scheme);
 303:                  await _emailSender.SendEmailAsync(model.Email, "Reset Password",
 304:                     $"Please reset your password by clicking here: <a href='{callbackUrl}'>link</a>");
 305:                  return View("ForgotPasswordConfirmation");
 306:              }
 307:   
 308:              // If we got this far, something failed, redisplay form
 309:              return View(model);
 310:          }
 311:          #endregion
 312:   
 313:          //
 314:          // GET: /Account/ForgotPasswordConfirmation
 315:          [HttpGet]
 316:          [AllowAnonymous]
 317:          public IActionResult ForgotPasswordConfirmation()
 318:          {
 319:              return View();
 320:          }
 321:   
 322:          //
 323:          // GET: /Account/ResetPassword
 324:          [HttpGet]
 325:          [AllowAnonymous]
 326:          public IActionResult ResetPassword(string code = null)
 327:          {
 328:              return code == null ? View("Error") : View();
 329:          }
 330:   
 331:          //
 332:          // POST: /Account/ResetPassword
 333:          [HttpPost]
 334:          [AllowAnonymous]
 335:          [ValidateAntiForgeryToken]
 336:          public async Task<IActionResult> ResetPassword(ResetPasswordViewModel model)
 337:          {
 338:              if (!ModelState.IsValid)
 339:              {
 340:                  return View(model);
 341:              }
 342:              var user = await _userManager.FindByEmailAsync(model.Email);
 343:              if (user == null)
 344:              {
 345:                  // Don't reveal that the user does not exist
 346:                  return RedirectToAction(nameof(AccountController.ResetPasswordConfirmation), "Account");
 347:              }
 348:              var result = await _userManager.ResetPasswordAsync(user, model.Code, model.Password);
 349:              if (result.Succeeded)
 350:              {
 351:                  return RedirectToAction(nameof(AccountController.ResetPasswordConfirmation), "Account");
 352:              }
 353:              AddErrors(result);
 354:              return View();
 355:          }
 356:   
 357:          //
 358:          // GET: /Account/ResetPasswordConfirmation
 359:          [HttpGet]
 360:          [AllowAnonymous]
 361:          public IActionResult ResetPasswordConfirmation()
 362:          {
 363:              return View();
 364:          }
 365:   
 366:          //
 367:          // GET: /Account/SendCode
 368:          [HttpGet]
 369:          [AllowAnonymous]
 370:          public async Task<ActionResult> SendCode(string returnUrl = null, bool rememberMe = false)
 371:          {
 372:              var user = await _signInManager.GetTwoFactorAuthenticationUserAsync();
 373:              if (user == null)
 374:              {
 375:                  return View("Error");
 376:              }
 377:              var userFactors = await _userManager.GetValidTwoFactorProvidersAsync(user);
 378:              var factorOptions = userFactors.Select(purpose => new SelectListItem { Text = purpose, Value = purpose }).ToList();
 379:              return View(new SendCodeViewModel { Providers = factorOptions, ReturnUrl = returnUrl, RememberMe = rememberMe });
 380:          }
 381:   
 382:          //
 383:          // POST: /Account/SendCode
 384:          [HttpPost]
 385:          [AllowAnonymous]
 386:          [ValidateAntiForgeryToken]
 387:          public async Task<IActionResult> SendCode(SendCodeViewModel model)
 388:          {
 389:              if (!ModelState.IsValid)
 390:              {
 391:                  return View();
 392:              }
 393:   
 394:              var user = await _signInManager.GetTwoFactorAuthenticationUserAsync();
 395:              if (user == null)
 396:              {
 397:                  return View("Error");
 398:              }
 399:   
 400:              // Generate the token and send it
 401:              var code = await _userManager.GenerateTwoFactorTokenAsync(user, model.SelectedProvider);
 402:              if (string.IsNullOrWhiteSpace(code))
 403:              {
 404:                  return View("Error");
 405:              }
 406:   
 407:              var message = "Your security code is: " + code;
 408:              if (model.SelectedProvider == "Email")
 409:              {
 410:                  await _emailSender.SendEmailAsync(await _userManager.GetEmailAsync(user), "Security Code", message);
 411:              }
 412:              else if (model.SelectedProvider == "Phone")
 413:              {
 414:                  await _smsSender.SendSmsAsync(await _userManager.GetPhoneNumberAsync(user), message);
 415:              }
 416:   
 417:              return RedirectToAction(nameof(VerifyCode), new { Provider = model.SelectedProvider, ReturnUrl = model.ReturnUrl, RememberMe = model.RememberMe });
 418:          }
 419:   
 420:          //
 421:          // GET: /Account/VerifyCode
 422:          [HttpGet]
 423:          [AllowAnonymous]
 424:          public async Task<IActionResult> VerifyCode(string provider, bool rememberMe, string returnUrl = null)
 425:          {
 426:              // Require that the user has already logged in via username/password or external login
 427:              var user = await _signInManager.GetTwoFactorAuthenticationUserAsync();
 428:              if (user == null)
 429:              {
 430:                  return View("Error");
 431:              }
 432:              return View(new VerifyCodeViewModel { Provider = provider, ReturnUrl = returnUrl, RememberMe = rememberMe });
 433:          }
 434:   
 435:          //
 436:          // POST: /Account/VerifyCode
 437:          [HttpPost]
 438:          [AllowAnonymous]
 439:          [ValidateAntiForgeryToken]
 440:          public async Task<IActionResult> VerifyCode(VerifyCodeViewModel model)
 441:          {
 442:              if (!ModelState.IsValid)
 443:              {
 444:                  return View(model);
 445:              }
 446:   
 447:              // The following code protects for brute force attacks against the two factor codes.
 448:              // If a user enters incorrect codes for a specified amount of time then the user account
 449:              // will be locked out for a specified amount of time.
 450:              var result = await _signInManager.TwoFactorSignInAsync(model.Provider, model.Code, model.RememberMe, model.RememberBrowser);
 451:              if (result.Succeeded)
 452:              {
 453:                  return RedirectToLocal(model.ReturnUrl);
 454:              }
 455:              if (result.IsLockedOut)
 456:              {
 457:                  _logger.LogWarning(7, "User account locked out.");
 458:                  return View("Lockout");
 459:              }
 460:              else
 461:              {
 462:                  ModelState.AddModelError(string.Empty, "Invalid code.");
 463:                  return View(model);
 464:              }
 465:          }
 466:   
 467:          //
 468:          // GET /Account/AccessDenied
 469:          [HttpGet]
 470:          public IActionResult AccessDenied()
 471:          {
 472:              return View();
 473:          }
 474:   
 475:          #region Helpers
 476:   
 477:          private void AddErrors(IdentityResult result)
 478:          {
 479:              foreach (var error in result.Errors)
 480:              {
 481:                  ModelState.AddModelError(string.Empty, error.Description);
 482:              }
 483:          }
 484:   
 485:          private IActionResult RedirectToLocal(string returnUrl)
 486:          {
 487:              if (Url.IsLocalUrl(returnUrl))
 488:              {
 489:                  return Redirect(returnUrl);
 490:              }
 491:              else
 492:              {
 493:                  return RedirectToAction(nameof(HomeController.Index), "Home");
 494:              }
 495:          }
 496:   
 497:          #endregion
 498:      }
 499:  }

Note: A security best practice is to not use production secrets in test and development. If you publish the app to Azure, you can set the SendGrid secrets as application settings in the Azure Web App portal. The configuration system is setup to read keys from environment variables.

Combine social and local login accounts

Note: This section applies only to ASP.NET Core 1.x. For ASP.NET Core 2.x, see this issue.

To complete this section, you must first enable an external authentication provider. See Enabling authentication using Facebook, Google and other external providers.

You can combine local and social accounts by clicking on your email link. In the following sequence, “RickAndMSFT@gmail.com” is first created as a local login; however, you can create the account as a social login first, then add a local login.

Web application: RickAndMSFT@gmail.com user authenticated
Web application: RickAndMSFT@gmail.com user authenticated

Click on the Manage link. Note the 0 external (social logins) associated with this account.

Manage view
Manage view

Click the link to another login service and accept the app requests. In the image below, Facebook is the external authentication provider:

Manage your external logins view listing Facebook
Manage your external logins view listing Facebook

The two accounts have been combined. You will be able to log on with either account. You might want your users to add local accounts in case their social log in authentication service is down, or more likely they have lost access to their social account.



Comments ( )
Link to this page: //www.vb-net.com/AspNet-DocAndSamples-2017/aspnetcore/security/authentication/accconfirm.htm
< THANKS ME>