Two-Factor Authentication in ASP.NET Core Identity

Two-Factor Authentication in ASP.NET Core Identity
The Two-Factor Authentication in ASP.NET Core Identity is a process where a user enters credentials, and after successful password validation, receives an OTP (one-time-password) via email or SMS. Then the user needs to enter that OTP in the Two-Step Verification form to log in. This procedure greatly increases the security of the application. Let’s now try to implement the Two-Factor Authentication in ASP.NET Core Identity.

Enable Two-Factor Authentication in Identity

The AspNetUsers table of the Identity Database has a column called TwoFactorEnabled which keeps track of a user’s Two-Factor Authentication status. A true value specifies if it is enabled while a false value specifies if it is disabled. The default value of TwoFactorEnabled column is false.

See the below image where I have shown this column and the values for different users.

twofactorenabled column

Note: The Two-Factor Authentication for a user also needs the EmailConfirmed column of the AspNetUsers to be set to true.

I have a Identity user who has both these columns set to “true”. See the below image where I have shown them. This user becomes a perfect case for Two-Factor Authentication.

emailconfirmed & twofactorenabled

You can set the TwoFactorEnabled column’s value to true for a user by providing the TwoFactorEnabled property of the User Class to “true”.

The below code snipped code I have shown how to update the TwoFactorEnabled property’s value to true for a user in the Identity Database.

AppUser user = await userManager.FindByIdAsync(id);
User.TwoFactorEnabled = true;
IdentityResult result = await userManager.UpdateAsync(user);

You can read more about the User Class of ASP.NET Core Identity in another article written by me.

Implementing Two-Factor Authentication in Identity

To implement the Two-Factor Authentication I will update my Login Action with is located inside the Account Controller. See the highlighted code given below which shows the newly added codes in the action method.

[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Login(Login login)
{
    if (ModelState.IsValid)
    {
        AppUser appUser = await userManager.FindByEmailAsync(login.Email);
        if (appUser != null)
        {
            await signInManager.SignOutAsync();
            Microsoft.AspNetCore.Identity.SignInResult result = await signInManager.PasswordSignInAsync(appUser, login.Password, false, true);
            if (result.Succeeded)
                return Redirect(login.ReturnUrl ?? "/");

            if (result.RequiresTwoFactor)
            {
                return RedirectToAction("LoginTwoStep", new { appUser.Email, login.ReturnUrl });
            }
        }
        ModelState.AddModelError(nameof(login.Email), "Login Failed: Invalid Email or password");
    }
    return View(login);
}  

You can clearly see I checked whether the user trying to login requires 2-Factor authentication. In that case I am redirecting him to the LoginTwoStep action method. See the below code.

if (result.RequiresTwoFactor)
{
    return RedirectToAction("LoginTwoStep", new { appUser.Email, login.ReturnUrl });
}

Next add the LoginTwoStep action method to the controller. Their code is given below.

[AllowAnonymous]
public async Task<IActionResult> LoginTwoStep(string email, string returnUrl)
{
    var user = await userManager.FindByEmailAsync(email);

    var token = await userManager.GenerateTwoFactorTokenAsync(user, "Email");

    EmailHelper emailHelper = new EmailHelper();
    bool emailResponse = emailHelper.SendEmailTwoFactorCode(user.Email, token);

    return View();
}

[HttpPost]
[AllowAnonymous]
public async Task<IActionResult> LoginTwoStep(TwoFactor twoFactor, string returnUrl)
{
    if (!ModelState.IsValid)
    {
        return View(twoFactor.TwoFactorCode);
    }

    var result = await signInManager.TwoFactorSignInAsync("Email", twoFactor.TwoFactorCode, false, false);
    if (result.Succeeded)
    {
        return Redirect(returnUrl ?? "/");
    }
    else
    {
        ModelState.AddModelError("", "Invalid Login Attempt");
        return View();
    }
}

The important thing to note is how I am creating the token using the GenerateTwoFactorTokenAsync() method of the UserManager<T> class.

var token = await userManager.GenerateTwoFactorTokenAsync(user, "Email");
Important Identity tutorial – How to work with Claims in ASP.NET Core Identity

And then sending this token to the user’s email address with the below code.

EmailHelper emailHelper = new EmailHelper();
bool emailResponse = emailHelper.SendEmailTwoFactorCode(user.Email, token);

I used the EmailHelper class kept on the Models folder. The work of this class would be to send the token to user’s email. The code of this class is given below.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Mail;
using System.Threading.Tasks;

namespace Identity.Email
{
    public class EmailHelper
    {
        // other methods

        public bool SendEmailTwoFactorCode(string userEmail, string code)
        {
            MailMessage mailMessage = new MailMessage();
            mailMessage.From = new MailAddress("[email protected]");
            mailMessage.To.Add(new MailAddress(userEmail));

            mailMessage.Subject = "Two Factor Code";
            mailMessage.IsBodyHtml = true;
            mailMessage.Body = code;

            SmtpClient client = new SmtpClient();
            client.Credentials = new System.Net.NetworkCredential("[email protected]", "yourpassword");
            client.Host = "smtpout.secureserver.net";
            client.Port = 80;

            try
            {
                client.Send(mailMessage);
                return true;
            }
            catch (Exception ex)
            {
                // log exception
            }
            return false;
        }
    }
}

Finally when the user enters the token it is validated by the TwoFactorSignInAsync method and user is login to his account.

var result = await signInManager.TwoFactorSignInAsync("Email", twoFactor.TwoFactorCode, false, false);

Next create a class called TwoFactor inside the Models folder. The code is below.

using System.ComponentModel.DataAnnotations;

namespace Identity.Models
{
    public class TwoFactor
    {
        [Required]
        public string TwoFactorCode { get; set; }
    }
}

Also add the view for the action. Name it TwoFactor and keep it inside the Views ➤ Account folder. It’s code is given below.

@model TwoFactor

@{
    ViewData["Title"] = "Login Two Step";
}

<h1 class="bg-info text-white">Login Two Step</h1>
<div class="text-danger" asp-validation-summary="All"></div>

<p>Enter your authentication code</p>

<form asp-action="LoginTwoStep" method="post">
    <div class="form-group">
        <label>Code</label>
        <input asp-for="TwoFactorCode" class="form-control" />
    </div>
    <button class="btn btn-primary" type="submit">Log In</button>
</form>

Testing the Entire functionality

On the login page as soon as we enter a valid credentials, you will see a new view that will ask for the authentication code. See the below image.

Two Factor Authentication Screen

Now go to your mail inbox to find this authentication code (shown by the image below).

two factor code

Finally, after you enter that token in the input field, you are going to be redirected either to the home view or to some other page. This depends on what you tried to access without authentication.

You can download the full codes of this tutorial from the below link:

Download

Conclusion

So, in this tutorial, you learned how to use a two-factor authentication process to authenticate a user by using an email provider. But, we don’t have to use only email provider. SMS is also an option that can be used for the process. The same logic applies to the SMS provider. All you have to do is change the email sending part given in the LoginTwoStep action with the SMS sending code. See below code:

[AllowAnonymous]
public async Task<IActionResult> LoginTwoStep(string email, string returnUrl)
{
    var user = await userManager.FindByEmailAsync(email);

    var token = await userManager.GenerateTwoFactorTokenAsync(user, "Email");

    //EmailHelper emailHelper = new EmailHelper();
    //bool emailResponse = emailHelper.SendEmailTwoFactorCode(user.Email, token);
    
    SMSHelper smsHelper = new SMSHelper();
    bool smsResponse = smsHelper.SendSMSTwoFactorCode(user.PhoneNumber, token);

    return View();
}

Share this article -

yogihosting

ABOUT THE AUTHOR

This article has been written by the Technical Staff of YogiHosting. Check out other articles on "ASP.NET Core, jQuery, EF Core, SEO, jQuery, HTML" and more.

Leave a Reply

Your email address will not be published. Required fields are marked *