Two-factor authentication - step by step

The following steps will enable your application to have complete two-factor authentication:

  1. Add a new model called TwoFactorCodeModel to the Models folder:
        public class TwoFactorCodeModel 
        { 
          [Key] 
          public long Id { get; set; } 
          public Guid UserId { get; set; } 
          [ForeignKey("UserId")] 
          public UserModel User { get; set; } 
          public string TokenProvider { get; set; } 
          public string TokenCode { get; set; } 
        } 
  1. Add a new model called TwoFactorEmailModel to the Models folder:
        public class TwoFactorEmailModel 
        { 
          public string DisplayName { get; set; } 
          public string Email { get; set; } 
          public string ActionUrl { get; set; } 
        } 
  1. Register TwoFactorCodeModel within GameDbContext by adding a corresponding DbSet:
        public DbSet<TwoFactorCodeModel> TwoFactorCodeModels { get; 
set; }
  1. Open the NuGet Package Manager Console and execute the Add-Migration AddTwoFactorCode command. Then, update the database by executing the Update-Database command.
  2. Update ApplicationUserManager, and then add a new method called SetTwoFactorEnabledAsync:
public override async Task<IdentityResult> SetTwoFactorEnabledAsync(UserModel user, bool enabled) 
{ 
  try 
  { 
    using (var db = new GameDbContext(_dbContextOptions)) 
    { 
      var current = await db.UserModels.FindAsync(user.Id); 
      current.TwoFactorEnabled = enabled; await 
db.SaveChangesAsync(); return IdentityResult.Success; } } catch (Exception ex) { return IdentityResult.Failed(new IdentityError {Description = ex.ToString() });} }

Then we perform the following steps:

    1. Add another method called GenerateTwoFactorTokenAsync
public override async Task<string>GenerateTwoFactorTokenAsync
(UserModel user, string tokenProvider) { using (var dbContext = new GameDbContext(_dbContextOptions)) { var emailTokenProvider = new EmailTokenProvider
<UserModel>(); var token = await emailTokenProvider.GenerateAsync
("TwoFactor", this, user); dbContext.TwoFactorCodeModels.Add(new TwoFactorCodeModel { TokenCode = token,TokenProvider = tokenProvider,UserId =
user.Id });
if (dbContext.ChangeTracker.HasChanges()) await dbContext.SaveChangesAsync(); return token; } }
    1. And finally, to the same ApplicationUserManager, add a VerifyTwoFactorTokenAsync method: 
        public override async Task<bool>
VerifyTwoFactorTokenAsync(UserModel user, string
tokenProvider, string token) { using (var dbContext = new
GameDbContext(_dbContextOptions)) { return await dbContext.TwoFactorCodeModels.AnyAsync(
x => x.TokenProvider == tokenProvider &&
x.TokenCode == token && x.UserId == user.Id); } }
  1. Go to the Areas/Account/Views/Home folder, and update the index view:
         
@inject UserManager<TicTacToe.Models.UserModel> UserManager 
@{ var isTwoFactor =UserManager.GetTwoFactorEnabledAsync(Model).Result;  ...   } 
<h3>Account Details</h3> 
<div class="container"> 
  <div class="row"> 
    <div class="col-xs-12 col-sm-6 col-md-6"> 
      <div class="well well-sm"> 
        <div class="row"> 
          ...
           <i class="glyphicon glyphicon-check"></i><text>Two
Factor Authentication </text> @if (Model.TwoFactorEnabled)<a asp-
action="DisableTwoFactor">Disable</a> else <a asp-action="EnableTwoFactor">Enable</a> </div> ...
  1. Add a new file called _ViewImports.cshtml to the Areas/Account/Views folder:
        @using TicTacToe 
        @using Microsoft.AspNetCore.Mvc.Localization 
        @inject IViewLocalizer Localizer 
        @addTagHelper *, TicTacToe 
        @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers 
  1. Update the UserService class and the user service interface, and then add a new method called EnableTwoFactor:
public async Task<IdentityResult> EnableTwoFactor(string name, bool enabled) 
{ 
  try 
  { 
    var user = await _userManager.FindByEmailAsync(name); 
    user.TwoFactorEnabled = true; 
    await _userManager.SetTwoFactorEnabledAsync(user, enabled); 
    return IdentityResult.Success; 
  } 
  catch (Exception ex) 
  { 
    throw; 
  } 
} 

Add another method, GetTwoFactorCode

public async Task<string> GetTwoFactorCode(string userName, string tokenProvider) 
{ 
  var user = await GetUserByEmail(userName); 
  return await _userManager.GenerateTwoFactorTokenAsync(user,tokenProvider); 
}                
  1. Update the SignInUser method in UserService for supporting two-factor authentication, if it is enabled:
        public async Task<SignInResult> SignInUser(LoginModel 
loginModel, HttpContext httpContext) { ... if (await _userManager.GetTwoFactorEnabledAsync(user)) return SignInResult.TwoFactorRequired; ... } ... }
  1. Go to the Areas/Account/Controllers folder, and update HomeController. Update the Index method and then add two new methods called EnableTwoFactor and DisableTwoFactor:
[Authorize] 
public async Task<IActionResult> Index() 
{ var user = await _userService.GetUserByEmail(User.Identity.Name); 
  return View(user); } 
 
[Authorize] 
public IActionResult EnableTwoFactor() 
{ 
  _userService.EnableTwoFactor(User.Identity.Name, true); 
  return RedirectToAction("Index"); 
} 
 
[Authorize] 
public IActionResult DisableTwoFactor() 
{ 
  _userService.EnableTwoFactor(User.Identity.Name, false); 
  return RedirectToAction("Index"); 
} 
Note that we will explain the [Authorize] decorator/attribute later in this chapter. It is used to add access restrictions to resources.
  1. Add a new model called ValidateTwoFactorModel to the Models folder:
        public class ValidateTwoFactorModel 
        { 
          public string UserName { get; set; } 
          public string Code { get; set; } 
        } 
  1. Update the AccountController, and add a new method called SendEmailTwoFactor:
private async Task SendEmailTwoFactor(string UserName) 
{ 
  var user = await _userService.GetUserByEmail(UserName); 
  var urlAction = new UrlActionContext 
  { Action = "ValidateTwoFactor", Controller = "Account",Values = 
new { email = UserName,
code = await _userService.GetTwoFactorCode(user.UserName,
"Email") }, Protocol = Request.Scheme, Host = Request.Host.ToString() }; var TwoFactorEmailModel = new TwoFactorEmailModel { DisplayName = $"{user.FirstName} {user.LastName}", Email =
UserName, ActionUrl = Url.Action(urlAction) }; var emailRenderService = HttpContext.RequestServices.
GetService<IEmailTemplateRenderService>(); var emailService = HttpContext.RequestServices.
GetService<IEmailService>(); var message = await emailRenderService.RenderTemplate(
"EmailTemplates/TwoFactorEmail", TwoFactorEmailModel,
Request.Host.ToString()); try{ emailService.SendEmail(UserName, "Tic-Tac-Toe Two Factor
Code", message).Wait(); } catch { } }
Note that in order to call RequestServices.GetService<T>();, you must also add using Microsoft.Extensions.DependencyInjection; as you have done previously in other examples.
  1. Update the Login method in AccountController:
[HttpPost] 
public async Task<IActionResult> Login(LoginModel loginModel) 
{ 
  if (ModelState.IsValid) 
  { 
    var result = await _userService.SignInUser(loginModel, 
HttpContext); if (result.Succeeded) { if (!string.IsNullOrEmpty(loginModel.ReturnUrl)) return
Redirect(loginModel.ReturnUrl); else return RedirectToAction("Index", "Home"); } else if (result.RequiresTwoFactor) await SendEmailTwoFactor
(loginModel.UserName); return RedirectToAction("ValidateTwoFactor"); else ModelState.AddModelError("", result.IsLockedOut ? "User is
locked" : "User is not allowed"); } return View(); }
  1. Add a new view called ValidateTwoFactor to the Views/Account folder:
@model TicTacToe.Models.ValidateTwoFactorModel 
@{ ViewData["Title"] = "Validate Two Factor";Layout = "~/Views/Shared/_Layout.cshtml"; } 
<div class="container"> 
  <div id="loginbox" style="margin-top:50px;" class="mainbox
col-md-6 col-md-offset-3 col-sm-8 col-sm-offset-2"> <div class="panel panel-info"> <div class="panel-heading"> <div class="panel-title">Validate Two Factor Code</div> </div> <div style="padding-top:30px" class="panel-body"> <div class="text-center"> <form asp-controller="Account"asp-
action="ValidateTwoFactor" method="post"> <div asp-validation-summary="All"></div> <div style="margin-bottom: 25px" class="input-group"> <span class="input-group-addon"><i class="glyphicon
glyphicon-envelope
color-blue"></i></span> <input id="email" asp-for="UserName"
placeholder="email address"
class="form-control" type="email">
</div> <div style="margin-bottom: 25px" class="input-group"> <span class="input-group-addon"><i class="glyphicon
glyphicon-lock
color-blue"></i></span> <input id="Code" asp-for="Code"
placeholder="Enter your code" class="form-
control"> </div> <div style="margin-bottom: 25px" class="input-group"> <input name="submit"class="btn btn-lg btn-primary
btn-block"
value="Validate your code" type="submit">
</div> </form> </div> </div> </div> </div> </div>
  1. Add a new view called TwoFactorEmail to the Views/EmailTemplates folder:
        @model TicTacToe.Models.TwoFactorEmailModel 
        @{ 
          ViewData["Title"] = "View"; 
          Layout = "_LayoutEmail"; 
        } 
        <h1>Welcome @Model.DisplayName</h1> 
        You have requested a two factor code, please click <a
href="@Model.ActionUrl">here</a> to continue.
  1. Update the UserService class and the user service interface, and then add a new method called ValidateTwoFactor:
public async Task<bool> ValidateTwoFactor(string userName,
string tokenProvider, string token, HttpContext
httpContext) { var user = await GetUserByEmail(userName); if (await _userManager.VerifyTwoFactorTokenAsync
(user,tokenProvider, token)) { ... } return false; }

  1. In the ValidateTwoFactor method, add the following actual code that performs the validation through identity claims: 
if (await _userManager.VerifyTwoFactorTokenAsync(user, 
tokenProvider, token))
{
var identity = new ClaimsIdentity
(CookieAuthenticationDefaults.Authentication
Scheme);
identity.AddClaim(new Claim(ClaimTypes.Name,
user.UserName));
identity.AddClaim(new Claim(ClaimTypes.GivenName,
user.FirstName));
identity.AddClaim(new Claim(ClaimTypes.Surname,
user.LastName));
identity.AddClaim(new Claim("displayName", $"
{user.FirstName} {user.LastName}"));

if (!string.IsNullOrEmpty(user.PhoneNumber))
identity.AddClaim(new Claim
(ClaimTypes.HomePhone, user.PhoneNumber));

identity.AddClaim(new Claim("Score",
user.Score.ToString()));
await httpContext.SignInAsync
(CookieAuthenticationDefaults.
AuthenticationScheme,
new ClaimsPrincipal(identity), new
AuthenticationProperties
{ IsPersistent = false });
return true;
}
  1. Update AccountController, and then add two new methods for two-factor authentication validation:
public async Task<IActionResult> ValidateTwoFactor(string email, string code) 
{ 
  return await Task.Run(() => 
  { return View(new ValidateTwoFactorModel { Code = code, UserName = 
email }); }); } [HttpPost] public async Task<IActionResult> ValidateTwoFactor(ValidateTwoFactorModel validateTwoFactorModel) { if (ModelState.IsValid) { await _userService.ValidateTwoFactor(validateTwoFactorModel
.UserName, "Email",
validateTwoFactorModel.Code, HttpContext); return RedirectToAction("Index", "Home"); } return View(); }
  1. Start the application, sign in as an existing user, and go to the Account Details page. Enable two-factor authentication (you might need to recreate the database and register a new user before this step):

  1. Sign out as the user, go to the login page, and then sign in again. This time, you will be asked to enter a two-factor authentication code:

  1. You will receive an email with the two-factor authentication code:

  1. Click on the link in the email and everything should be filled in for you automatically. Sign in and verify that everything is working as expected:

You can see how easy it is to implement two-factor authentication, as we did in the last section. Having gone through different forms of authentication, there will always be times when you may forget your password, and therefore we need to be able to reset our passwords securely so that we can be allowed back into our application after we re-authenticate our credentials. We will cover this in the next section.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.133.141.212