Reduce The 2FA'S Token Provider Length in ASP.NET Core Identity To 4 Digits Instead Of 6 Digits in .NET 7
Tags: aspnetcore, csharp, dotnet, dotnetcore
Published: 2022-12-13 21:39:00

In this new post I will show you how to work with two factor authentication and how to reduce the token provider length from 6 digits to 4 digits.
In the first step we have to create our sample application from scratch by creating a new Empty ASP.NET Core Application from visual Studio 2022 and using .NET 7.

First of all, to follow with me, please download the complete code form my GitHub repo from [AspNetCoreIdentity-IUserTwoFactorTokenProvider](https://github.com/Shoogn/AspNetCoreIdentity-IUserTwoFactorTokenProvider) 

I'm not going here to explain how to create a new user in ASP.NET Core Identity but I will guide you how to accomplish the two-factor authentication step after success login from the user.
And ensure you have one user at least in your Back Store with enabled **TowFactorEnabled** property to true, see the pic below:

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


In the root of the application create a new folder named Services and inside this folder create a new class and interface as you see in the following code snippet (I will explain later...)

```C#
namespace AspNetCoreIdentity.Services
{
    public interface ITokenStoreService
    {
        bool StoreToken(string key, string token);
        bool RemoveToken(string key);
        string GetToken(string key);
    }
}
``` 
And here the implementation of this interface like below:

```C#
using System.Collections.Concurrent;

namespace AspNetCoreIdentity.Services
{
    public class TokenStoreService : ITokenStoreService
    {
        private readonly ConcurrentDictionary<string, string> _tokenProviderStore = new ConcurrentDictionary<string, string>();

        public bool RemoveToken(string key)
        {
            var result = _tokenProviderStore.TryRemove(key, out _);
            return result; 
        }

        public bool StoreToken(string key, string token)
        {
            if (!_tokenProviderStore.ContainsKey(key))
            {
                var result = _tokenProviderStore.TryAdd(key, token);
                return result;
            }
            return false;
        }

        public string GetToken(string key)
        {
            var result = _tokenProviderStore.TryGetValue(key, out string token );
            if(result)
                return token;
            return null;
        }
    }
}
```
Make sure to register TokenStoreService.cs class at Program.cs file like below:
```C#
builder.Services.AddSingleton<ITokenStoreService, TokenStoreService>();
```
This class will help us to store the generated token provider in the **ConcurrentDictionary**

In the root of the application create a new folder named Helpers and inside this folder create a new class named **CustomTwoFactorTokenProvider.cs** this class has the following signature:

```C#
using AspNetCoreIdentity.Models;
using AspNetCoreIdentity.Services;
using Microsoft.AspNetCore.Identity;
using System;
using System.Threading.Tasks;

namespace AspNetCoreIdentity.Helpers
{
    public class CustomTwoFactorTokenProvider 
    : IUserTwoFactorTokenProvider<ApplicationUser>
    {
        private readonly ITokenStoreService _tokenStoreService;
        public CustomTwoFactorTokenProvider(ITokenStoreService tokenStoreService)
        {
            _tokenStoreService = tokenStoreService;
        }
        public Task<bool> CanGenerateTwoFactorTokenAsync(UserManager<ApplicationUser> manager, ApplicationUser user)
        {
            return Task.FromResult(manager.SupportsUserTwoFactor);
        }

        public Task<string> GenerateAsync(string purpose, UserManager<ApplicationUser> manager, ApplicationUser user)
        {
            Random random = new Random();
            int token = random.Next(1000, 9999);

            if (_tokenStoreService.StoreToken(user.Id, token.ToString()))
                return Task.FromResult(token.ToString());

            return Task.FromResult(string.Empty);
        }

        public Task<bool> ValidateAsync(string purpose, string token, UserManager<ApplicationUser> manager, ApplicationUser user)
        {
            var storedToken = _tokenStoreService.GetToken(user.Id);
            if (storedToken != null)
            {
                bool result = storedToken.Equals(token);
                _tokenStoreService.RemoveToken(user.Id);

                return Task.FromResult(result);
            }
            return Task.FromResult(false);
        }
    }
}
```
When your application supports the 2FA the ASP.NET Core Identity will help you to generate a token by calling a method from the **UserManager** object named **GenerateTwoFactorTokenAsync** this method will generate a code that contains **6** digits, and as you working in this field (Software programming) you will meet at least one customer, who don't like this approach, that enforce the user to insert 6 digits to complete their login process, and maybe he will ask you to reduce the number to 4 digits. 
So how you can fix that? And from where the token that has contains 6 digits come?
When you register the ASP.NET Core Identity at the level of your application inside the Program.cs class we inform the ASP.NET Core Identity to accept the default token provider by add this extension **AddDefaultTokenProviders** to the **AddIdentity** extension the **AddDefaultTokenProviders** extension has all token providers that our applications need to work with them like working with **Email Confirmation Provider** and **Reset Password Token Provider** and Registration any type of **Authenticators Provider** ( like google Authenticator, you can read my article about how to configure Google Authenticator with ASP.NET Core Identity from [here](https://dev.to/mohammedahmed/aspnet-core-identity-2fa-with-google-authenticator-i13).
So, in our case we need to work with **TwoFactorUserId Provider** and this will enforce our users to complete their login process in two steps.
ASP.NET Core Identity provide us with an interface name **IUserTwoFactorTokenProvider** and by inheriting from this interface we can change the default implementation of the **TwoFactorUserId Provider** as you see in the above code and this interface has three members like below:

```C#
Task<bool> CanGenerateTwoFactorTokenAsync(UserManager<TUser> manager, TUser user);
Task<string> GenerateAsync(string purpose, UserManager<TUser> manager, TUser user);
Task<bool> ValidateAsync(string purpose, string token, UserManager<TUser> manager, TUser user);
``` 
The first method **CanGenerateTwoFactorTokenAsync** will be calling when we call the **GetTwoFactorEnabledAsync** method from the **UserManager** object and this method (**GetTwoFactorEnabledAsync**) will return _IList_ of the all available providers for the current user that he wants to login in our application, and the type of the provider in our case is **Identity.TwoFactorUserId** beacuse when we register the ASP.NET Core Identity we use the custom two factor authentication provider with name **Identity.TwoFactorUserId**, like so in the Program.cs file:

```C#
builder.Services.AddIdentity<ApplicationUser, IdentityRole>(options =>
{
    options.SignIn.RequireConfirmedAccount = true;
    options.SignIn.RequireConfirmedPhoneNumber = true;
    options.Password.RequireDigit = true;
    options.Password.RequireLowercase = true;
    options.Password.RequiredLength = 5;
    options.Password.RequireUppercase = false;
    options.Password.RequireNonAlphanumeric = false;
    options.Lockout.MaxFailedAccessAttempts = 5;
    options.User.RequireUniqueEmail = true;
}).AddRoles<IdentityRole>().AddEntityFrameworkStores<BaseDbContext>()
.AddTokenProvider<CustomTwoFactorTokenProvider>(IdentityConstants.TwoFactorUserIdScheme);

builder.Services.AddAuthentication(opt =>
{
    opt.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
}).AddCookie(options =>
{
    options.LoginPath = "/Accounts/Login";
});
```
Keep your eyes open at our default token provider here, we use the **AddTokenProvider** instead of the **AddDefaultTokenProviders**

```C#
.AddTokenProvider<CustomTwoFactorTokenProvider>(IdentityConstants.TwoFactorUserIdScheme);
```
The **CanGenerateTwoFactorTokenAsync** metho it will depend on your business and as you can see here, I return result of Boolean (true) if the current has TowFactorEnabled's property set to true in the back store (SQL Server in my case), feel free to apply any business fits your needs.

So, after we return _IList_ of the all available providers we will generating the **token provider** by calling **GenerateTwoFactorTokenAsync** method from the UserManager object and in the same way this method will call **GenerateAsync** method internally from the **CustomTwoFactorTokenProvider.cs** class after that we sending the token provier to our user (by SMS or Email service) then, the user will apply this token (code) in a completely new screen, and your job is to validate the token that coming from the user with the one that you're generate it and sending it to that user, so here you will call the **VerifyTwoFactorTokenAsync** method from the UserManager object also, and by calling this method it will call the **ValidateAsync** from the our custom class **CustomTwoFactorTokenProvider.cs** that has implements the **IUserTwoFactorTokenProvider<TUser>** interface.
So, we need a to store the custom token provider that we generate and send it to our user in a safe place, for that reason I create the **TokenStoreService.cs** class that you see it at the beginning of this article. Be sure to remove the token provider from the **ConcurrentDictionary** after validation it to avoids any duplication of the key in that dictionary.

Let us give it a try by firing the application and navigate to the https:localhost:port/Home/Home/SecureContent

Here is the Login screen:

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

And as you can see here, I dropped the token provider to Debug output to five myself a chance to apply it in the two-step screen

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6y1k1w2j8qa388hgqxc8.png)
The code is **2607**

And here is the two-step screen:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/palqviup6ucxqu5o0onb.png)
And last but not least the **SecureContent** page with my name as current logged-in user


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