ASP.NET Core Identity Series Nine

In this section, we mainly use ASP.NET Core Identity 2FA (two-factor authentication). Two-factor authentication is a user authentication process. When the user enters the account and password on the login page and the verification is successful, the user will receive an email and SMS. One-time password OTP (one-time-password). The user needs to enter this one-time password OTP in the second step of login verification, which greatly increases the security of the application

1. Enable two-factor authentication in Identity

cbe397d4a86dc8bb5b4895f8fecb2b28.png

In the same way, we can also update an Identity user and set its TwoFactorEnabled to true, the code is as follows:

9058e011066714689e63b3bfa73e59bd.png

When enabling two-factor authentication, we need to set EmailConfirmed to True

3f916019cee2826639dd946bcd57e6a3.png

2. Implement Identity two-factor authentication

In order to implement ASP.NET Core Identity two-factor authentication, we need to modify the Login method in AccountController, the code is as follows:

[HttpPost]
[Allow Anonymous]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Login(Login login)
{
    if (ModelState. IsValid)
    {
        var appUser = await _userManager. FindByEmailAsync(login. Email);
        if (appUser != null)
        {
            await _signInManager. SignOutAsync();
            var signInResult = await _signInManager.PasswordSignInAsync(appUser, login.Password,
                login. RememberMe, false);
            if (signInResult. Succeeded)
            {
                return Redirect(login. ReturnUrl  "/");
            }
            if (appUser.TwoFactorEnabled)
            {
                return RedirectToAction("LoginTwoStep", new { Email = appUser.Email, ReturnUrl = login.ReturnUrl });
            }
        }
        ModelState.AddModelError(nameof(login.Email), "Login Failed: Invalid Email or password");
    }
    return View(login);
}

We can see that during the login process, we check whether the user has enabled double authentication. If so, we will jump to LoginTwoStep. Let’s see how LoginTwoStep works. Add the LoginTwoStep method in AccountController, the code is as follows:

[AllowAnonymous]
public async Task<IActionResult> LoginTwoStep(string email, string returnUrl)
{
    var appUser = await _userManager. FindByEmailAsync(email);
    //Create Token
    var token = await _userManager.GenerateTwoFactorTokenAsync(appUser  new AppUser(), "Email");
    //send email
    _emailService.Send(appUser?.Email  "[email protected]", "Authorization Code", $"<h2>{token}</h2>");
    //send SMS
    //_smsService.Send(appUser?.PhoneNumber  "13333333333", token);
    return View("LoginTwoStep", new TwoFactor { ReturnUrl = returnUrl });
}
[HttpPost]
[Allow Anonymous]
public async Task<IActionResult> LoginTwoStep(TwoFactor twoFactor, string returnUrl)
{
    if (!ModelState. IsValid)
    {
        return View("LoginTwoStep", new TwoFactor { TwoFactorCode = twoFactor.TwoFactorCode, ReturnUrl = returnUrl });
    }
    var result = await _signInManager.TwoFactorSignInAsync("Email", twoFactor.TwoFactorCode, false, false);
    if (result. Succeeded)
    {
        return Redirect(returnUrl  "/");
    }
    else
    {
        ModelState.AddModelError("", "Login failed");
        return View();
    }
}

Note: Use UserManager class GenerateTwoFactorTokenAs

async() method to create a token, and then we send the token to the user’s mailbox,

We use the EmailService class located in the CommonService folder in the root directory. The main job of this class is to send the token to the email address registered by the user. The code of this class is as follows:

public class EmailSetting
{
    public string EmailFrom { get; set; } = null!;
    public string EmailTo { get; set; } = null!;
    public string SmtpHost { get; set; } = null!;
    public int SmtpPort { get; set; }
    public string SmtpUser { get; set; } = null!;
    public string SmtpPass { get; set; } = null!;
}
public interface IEmailService
{
    void Send(string to, string subject, string html, string from = null);
}
public class EmailService : IEmailService
{
    private readonly EmailSetting _appSettings;


    public EmailService(IOptions<EmailSetting> options)
    {
        _appSettings = options. Value;
    }
    public void Send(string to, string subject, string html, string from = null)
    {
        //Create Message
        var email = new MimeMessage();
        email.From.Add(MailboxAddress.Parse(_appSettings.EmailFrom));
        email.To.Add(MailboxAddress.Parse(to));
        email.Subject = subject;
        email.Body = new TextPart(TextFormat.Html) { Text = html };


        //Send Mail


        using var smtp = new SmtpClient();
        smtp.Connect(_appSettings.SmtpHost, _appSettings.SmtpPort, MailKit.Security.SecureSocketOptions.StartTls);
        smtp.Authenticate(_appSettings.SmtpUser, _appSettings.SmtpPass);
        smtp.Send(email);
        smtp. Disconnect(true);
    }
}

Finally, TwoFactorSignInAsync will verify the user’s token, and the TwoFactor class is as follows:

public class TwoFactor
{
    [Required]
    [DisplayName("Authorization Code")]
    public string TwoFactorCode { get; set; } = null!;
    public string? ReturnUrl { get; set; }
}

We create a new LoginTwoStep.cshtml view in Views->Account

code show as below:

@model TwoFactor
@{
    ViewData["Title"] = "Enter authorization code";
}
<div class="container">
    <form asp-action="LoginTwoStep" method="post">
        <div class="mb-3 row align-items-center">
            <div class="col-sm-1">
                <label asp-for="TwoFactorCode" class="control-label"></label>
                <input type="hidden" name="returnUrl" value="@Model.ReturnUrl" />
            </div>
            <div class="col-sm-11">
                <input asp-for="TwoFactorCode" class="form-control" value="@Model.TwoFactorCode" />
            </div>
        </div>
        <div class="mb-3 row align-items-center">
            <div class="col-sm-11 offset-sm-1">
                <button class="btn btn-primary" type="submit">Login</button>
            </div>
        </div>
    </form>
</div>

3. Test the entire function

On the login page we enter valid login credentials and we see a new page asking us to enter an authentication code. As shown below

fd8e933dd995765b47711cfc3d703d5e.png

Enter the mailbox to find the authentication code, we enter the authentication code, and it will jump to the home page or other pages, depending on whether the resources you visit have permission

73e9b6d861a4fcccccc8de5c1d8d6381.png

So far, in this section we have learned how to use Identity to complete two-factor authentication through the email provided by the user. However, in addition to email, we can also use SMS. SMS can also be used for this process. The same logic process uses the SMS provider. We only need to modify part of the email sending part, as follows:

dba1622e31d7799a3688578d51c401d2.png

Summary

In this section, we mainly introduce the two-factor authentication of users using email. In the same way, we can perform SMS authentication without modifying any logic

Source code address:

https://github.com/bingbing-gui/Asp.Net-Core-Skill/tree/master/AspNetCore.Identity/Identity