ASP.NET Core Identity 2FA with Google Authenticator
Tags: csharp, dotnet
Published: 2021-12-07 19:40:31

Hi, in this post I will show you how to setup ASP.NET Core Identity **2FA** by using _**Google authenticator**_.
2FA is a technique and more secure way and enforce the users to access their accounts with two steps.

#Our Example
I suppose you have a ready application with ASP.NET Core Identity with Register and Login functionality (without 2FA). And also (I suppose...) you have a tested user that has **TwoFactorEnabled** assigned to **false** in you Database.
So let's started.

First, we need to enable the 2FA in our application, in ASP.NET Core Identity you can achieve that by three ways.
1. Email
2. Phone
3. Authenticator, and this is what we are going to do in this post.

To let user work with 2FA we need a setup page that give the user the ability to enable the 2FA for his account.
So, create a new class with this signature:

```
public class ConfigureTwoFactorAuthenticatorModel
    {
        /// <summary>
        /// The key that we have to store it in our Database
        /// </summary>
        public string AuthenticatorKey { get; set; }

        /// <summary>
        /// Generated key the user must insert it in his authenticator app
        /// You can use any QRCode library the facilate the reading of this key instead of typing it.
        /// Try the QRCode js library
        /// </summary>
        public string Code { get; set; }
    }

``` 
Ok, now we have to create an Action Method with html page.

```
[HttpGet]
        [Authorize]
        public async Task<IActionResult> TwoFactorAuthenticationSetupPage()
        {
            var user = await _userManager.GetUserAsync(User);
            if (user == null)
            {
                // Redirect user to any page you want...
                return RedirectToAction("/");
            }
            else
            {
                var authenticatorKey = await _userManager.GetAuthenticatorKeyAsync(user);
                if (authenticatorKey == null)
                {
                    await _userManager.ResetAuthenticatorKeyAsync(user);
                    authenticatorKey = await _userManager.GetAuthenticatorKeyAsync(user);
                }
 // For demo purpose writh the key to attached debugger
                    Debug.WriteLine(authenticatorKey);
                return View(new ConfigureTwoFactorAuthenticatorModel { AuthenticatorKey = authenticatorKey });

            }
        }
```

The purpose of this action is to create an AuthenticatorKey for current user, by calling **_userManager.GetAuthenticatorKeyAsync(user)** this method will retrieve the key from the back store and the return of GetAuthenticatorKeyAsync method is string so if the return type is null generate a new authenticator key by calling the  new method **ResetAuthenticatorKeyAsync** and call again **GetAuthenticatorKeyAsync** method to retrieve the key after saved it in back store. We drop the authenticator key to writeline of debugger for demo purpose (here you have to use an QRCode library to let your users scan the image and type the code is the code's field in the html page (coming next). Pretty easy!

Before digging into html page of the [**TwoFactorAuthenticationSetupPage**] action method let us finished from the HTTPOST method, the code is below:

```
[HttpPost]
        [Authorize]
        public async Task<IActionResult> TwoFactorAuthenticationSetupPage(ConfigureTwoFactorAuthenticatorModel model)
        {
            if (ModelState.IsValid)
            {
                var user = await _userManager.GetUserAsync(User);
                bool isValidCode = await _userManager.VerifyTwoFactorTokenAsync(user, 
                    _userManager.Options.Tokens.AuthenticatorTokenProvider,
                    model.Code);

                if (isValidCode)
                {
                    await _userManager.SetTwoFactorEnabledAsync(user, true);
                    return RedirectToAction("Index", "Home");
                }
                else
                {
                    ModelState.AddModelError("", "Error in Code");
                    return View(model);
                }
            }
            ModelState.AddModelError("", "General Error");
            return View(model);
        }
```
This method is straightforward , check the model state and validate the token by calling **VerifyTwoFactorTokenAsync** the second argument of this method  specify the name of the authenticator. which is come from the **AddDefaultTokenProviders**,the extension you added to your startup class when you configure the ASP.NET Core Identity.
Now the time for the html page ( no need to explain it, it's pretty easy to get it's grasp)

```
@model Platform.ViewModel.ConfigureTwoFactorAuthenticatorModel
@{
    ViewData["Title"] = "TwoFactorAuthenticationSetupPage";
    Layout = "~/Views/Shared/_Layout.cshtml";
}

<div class="row justify-content-md-center" style="margin-top:130px;">

    <div class="col-md-5">
        <div class="card">
            <h5 class="card-header bg-dark text-white">TwoFactorAuthenticationSetupPage</h5>
            <div class="card-body">

                <form class="px-4 py-3" asp-action="TwoFactorAuthenticationSetupPage" asp-controller="Account" method="post" id="frmLogin">
                    <div asp-validation-summary="All" class="text-danger"></div>
                   
                    <input type="hidden" asp-for="AuthenticatorKey" />
                    <div class="form-group">
                        <input class="form-control" asp-for="Code">
                    </div>
           
                    <button type="submit" class="btn btn-primary">Submit</button>

                </form>
   
            </div>
        </div>
    </div>


</div>

```
Let' run the app and see what happened?!


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ihn75pf5jvdnzdv2ojfk.PNG)

This is the **AuthenticatorKey** that we write it to Debugger.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ttu11f49zp99egmmr8yf.PNG)

This is the code the Google Authenticator App generated to me after I inserted the **AuthenticatorKey** to Google Authenticator App.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7s6ydov075q9jztcjggs.PNG)
This is the **AuthenticatorKey** saved to BackStore in **AspNetUserTokens** Table.

So, now we prepare our app and registration to work with the any Authenticator app. The next step is Signing with Authenticator App.

First, let me paste my LoginModel.cs class

```
public class LoginModel
    {
        [Required]
        public string Email { get; set; }
        [Required]
        public string Password { get; set; }
    }
```
Straightforward class that let user provide his email and password.
And this is the **Login** [HTTPPOST] Action Method
```
 [AllowAnonymous]
        [ValidateAntiForgeryToken]
        [HttpPost]
        public async Task<IActionResult> Login(LoginModel model, string returnUrl)
        {
            if (ModelState.IsValid)
            {
                AppUser user = await _userManager.FindByEmailAsync(model.Email);
                if(user!= null)
                {
                    await _signInManager.SignOutAsync();

                    Microsoft.AspNetCore.Identity.SignInResult result = 
                                     await _signInManager.PasswordSignInAsync(user, model.Password, false, false);

                    if (result.RequiresTwoFactor)
                    {
                        return RedirectToAction("LoginTwoStep", new { returnUrl = returnUrl });
                    }

                    

                    if (result.Succeeded)
                    {
                        return Redirect(returnUrl ?? "/");
                    }
                }

                ModelState.AddModelError(nameof(LoginModel.Email), "Invalid user name or password");
            }

            return View(model);
        }
```
Here after user looged in we check if he has 2FA enabled by calling this property **RequiresTwoFactor** and if the result of this property return true so we have to redirect the user to **LoginTwoStep** Action Method.


and here is the HTTPGET and HTTPPOST of the **LoginTwoStep** Action method

```
[HttpGet]
        public IActionResult  LoginTwoStep(string returnUrl)
        {
            return View(new LoginTwoStepModel { ReturnUrl = returnUrl});
        }
        [HttpPost]
        public async Task<IActionResult> LoginTwoStep(LoginTwoStepModel model)
        {
            if (ModelState.IsValid)
            {
                var user = await _signInManager.GetTwoFactorAuthenticationUserAsync();
                if(user!= null)
                {
                    var result = await _signInManager.TwoFactorAuthenticatorSignInAsync(model.Token, true, false);
                    if (result.Succeeded)
                    {
                        return Redirect(model.ReturnUrl);
                    }
                    // show the error to user
                    ModelState.AddModelError("", "General Error");
                    return View(model);
                }
            }
            // show the error to user
            ModelState.AddModelError("", "General Error");
            return View(model);
        }
```
In the POST method we return an user for the current 2FA login by calling GetTwoFactorAuthenticationUserAsync method and if this method return null we have to cancel the operation of the login otherwise we have to complete the process of the login as you see in the next pictures


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ma682knnjjqt4cy7woa6.PNG)

Here I have to insert the code from the Google Authenticator App, see next picture:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/33xl60rsm0s2wrgutm28.jpg)

Sorry for the dirty pic, because I can't take an screenshot from the Google Authenticator App I took a photo by other phone.

In the HTTPPOST of the Login Action Method, instead of using:

```
 if (result.RequiresTwoFactor)
                    {
                        return RedirectToAction("LoginTwoStep", new { returnUrl = returnUrl });
                    }
```

You can check by the following code:

```
    if (await _userManager.GetTwoFactorEnabledAsync(user))
                    {
                        var validProvider = await _userManager.GetValidTwoFactorProvidersAsync(user);

                        if (validProvider.Contains(_userManager.Options.Tokens.AuthenticatorTokenProvider))
                        {
                            return RedirectToAction("LoginTwoStep", new { returnUrl = returnUrl });
                        }
                    }
```

 Here first we check if the current user has enabled the 2FA in back store (database), then we made a second check if the available 2FA is **AuthenticatorTokenProvider**  and if so then redirect the user to the LoginTowStep Action Method.