Open Closed

Making TOTP 2FA provider independent from other two and modifying 2FA force behavior #9845


User avatar
0
shamyratpashiyev created

Greetings, there are some problems I encountered in layered ABP solution:

  1. I need to disable email and phone number providers in 2FA and leave only TOTP provider functional, ie. I need to make it so that users can enable TOTP provider without verifying phone number or email. How can I achieve that? Please provide detailed instructions.
  2. And I need to make it so force behavior in 2FA works with TOTP, i.e when 2FA behavior is set to force, I need the app to redirect the user to Two Factor Authentication page at "/Account/Manage" and so the user is not able to log in without enabling TOTP app. Please give me detailed instructions on this as well.

29 Answer(s)
  • User Avatar
    0
    AI-Bot created
    Support Team ABP Agent
    1. To disable email and phone number providers in 2FA and leave only the TOTP provider functional, you need to override the logic that provides available two-factor authentication providers. In your application layer, override the method that returns the list of 2FA providers (such as GetTwoFactorProvidersAsync in your AccountAppService or equivalent). Only return the TOTP provider in the result. This will prevent users from seeing or enabling email or phone-based 2FA, and allow enabling TOTP without verifying email or phone.

    Example:

    public override async Task<ListResultDto<string>> GetTwoFactorProvidersAsync()
    {
        // Only return TOTP provider
        return new ListResultDto<string>(new List<string> { "Authenticator" });
    }
    

    Make sure your UI only displays the TOTP option and does not require email or phone verification for enabling TOTP.

    1. To force 2FA with TOTP and redirect users to the Two Factor Authentication page at "/Account/Manage" when 2FA is required, set the 2FA behavior to "force" in your configuration. In your Angular application, after login, check if the user has 2FA enabled and is required to complete 2FA. If so, redirect them to "/Account/Manage" for TOTP verification.

    You may need to customize the authentication flow in your Angular app to handle this redirect. Typically, after login, if the backend indicates that 2FA is required (e.g., via a specific response or claim), perform a client-side redirect:

    // Pseudocode for Angular
    if (userRequires2FA) {
      this.router.navigate(['/account/manage']);
    }
    

    Ensure your backend enforces 2FA when required and that the Angular app checks for this state after authentication.

    Sources:

    • https://abp.io/docs/latest/modules/identity/two-factor-authentication
    • https://abp.io/support/questions/4444

    This response was generated by AI. If it didn’t solve your problem, feel free to post a new answer — our support team will jump in to help you soon.

  • User Avatar
    0
    shamyratpashiyev created

    No I tried that, didn't work

  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    hi

    You can remove the email and phone from options.Tokens.ProviderMap

    services.PostConfigure<IdentityOptions>(options =>
    {
        //options.Tokens.ProviderMap
    });
    
  • User Avatar
    0
    shamyratpashiyev created

    Hi, if I do that it throws "NotSupportedException: No IUserTwoFactorTokenProvider<TUser> named 'Default' is registered." error. The reason is that I still need to verify either email or phone number to enable TOTP and if I do that it throws the error above. What I need is to make TOTP provider totally independent from email and phone providers. I mean so the user can enable only TOTP without verifying email or phone number.

  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    hi

    You can consider overriding the SendSecurityCodeModel to remove the 2FA providers.

  • User Avatar
    0
    shamyratpashiyev created

    Hi. It is a .cshtml page right? If yes can you please specify its location and exact name so I can override that page and test it? And it would also mean that I need to override the .cshtml file as well, in that case I need the content of that file too. I checked your repository in github but couldn't find it.

  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    hi

    The page class is Volo.Abp.Account.Public.Web.Pages.Account.SendSecurityCodeModel

    You can create a subclass to override OnGetAsync method.

    https://abp.io/docs/latest/framework/ui/mvc-razor-pages/customization-user-interface

  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    You don't need to override the cshtml page. Just need the SendSecurityCodeModel

    Thanks.

  • User Avatar
    0
    shamyratpashiyev created

    I have removed email and phone operators from the list and they're not being shown on Two Factor Authentication page (which comes after login), but I have another problem, now the user has to have email verified by default, but if the user has not enabled TOTP, it still shows Two Factor Authentication page (which comes after login) with an empty list of providers, how do I solve this, maybe I need to override Two Factor Authentication page?

  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    hi

    now the user has to have email verified by default, but if the user has not enabled TOTP, it still shows Two Factor Authentication page (which comes after login) with an empty list of providers, how do I solve this, maybe I need to override Two Factor Authentication page?

    Why did this user enable two-factor authentication?


    You can try to override the GetValidTwoFactorProvidersAsync method of IdentityUserManager

    Remove email and phone from TwoFactorProviders, after that, you don't need to override the page.

    Thanks.

    using System;
    using System.Collections.Generic;
    using System.Threading.Tasks;
    using Microsoft.AspNetCore.Identity;
    using Microsoft.Extensions.Logging;
    using Microsoft.Extensions.Options;
    using Volo.Abp.Caching;
    using Volo.Abp.DependencyInjection;
    using Volo.Abp.EventBus.Distributed;
    using Volo.Abp.Security.Claims;
    using Volo.Abp.Settings;
    using Volo.Abp.Threading;
    
    namespace Volo.Abp.Identity;
    
    [Dependency(ReplaceServices = true)]
    [ExposeServices(typeof(IdentityUserManager))]
    public class MyIdentityUserManager : IdentityUserManager
    {
        public MyIdentityUserManager(IdentityUserStore store,
            IIdentityRoleRepository roleRepository,
            IIdentityUserRepository userRepository,
            IOptions<IdentityOptions> optionsAccessor,
            IPasswordHasher<IdentityUser> passwordHasher,
            IEnumerable<IUserValidator<IdentityUser>> userValidators,
            IEnumerable<IPasswordValidator<IdentityUser>> passwordValidators,
            ILookupNormalizer keyNormalizer,
            IdentityErrorDescriber errors,
            IServiceProvider services,
            ILogger<IdentityUserManager> logger,
            ICancellationTokenProvider cancellationTokenProvider,
            IOrganizationUnitRepository organizationUnitRepository,
            ISettingProvider settingProvider,
            IDistributedEventBus distributedEventBus,
            IIdentityLinkUserRepository identityLinkUserRepository,
            IDistributedCache<AbpDynamicClaimCacheItem> dynamicClaimCache)
            : base(store, roleRepository, userRepository, optionsAccessor, passwordHasher, userValidators,
                passwordValidators, keyNormalizer, errors, services, logger, cancellationTokenProvider,
                organizationUnitRepository, settingProvider, distributedEventBus, identityLinkUserRepository,
                dynamicClaimCache)
        {
        }
    
        public override async Task<IList<string>> GetValidTwoFactorProvidersAsync(IdentityUser user)
        {
            var providers = await base.GetValidTwoFactorProvidersAsync(user);
            providers.RemoveAll(x => x == TokenOptions.DefaultEmailProvider || x == TokenOptions.DefaultPhoneProvider);
            return providers;
        }
    }
    
    
  • User Avatar
    0
    shamyratpashiyev created

    User cannot enable 2FA in /Account/Manage page if the email is not verified, i.e TOTP is totally dependent on email and phone providers, which I am trying to change and also Force behavior doesn't work without email or phone. And also I tried the code above it still shows Two Factor Authentication page (which comes after login) with an empty list of providers

  • User Avatar
    0
    shamyratpashiyev created

    Or there is other way of solving this problem, I can override the /Account/Manage page model and make it so it displays 2FA page there without verifying email or phone but the Force behavior is still tied to email or phone being verified. Can you help me with making so the Force behavior works with TOTP as well (when the email and phone are not verified). I mean so it redirects the user to 2FA page if 2FA behavior is set to force and the user hasn't enabled TOTP yet?

  • User Avatar
    0
    shamyratpashiyev created

    Or I can set all the user's emails to verified by default and remove the email and phone from GetValidTwoFactorProvidersAsync(), but then if the user hasn't enabled TOTP it shows empty list of providers.

  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    hi

    I will check and provide a solution.

    Thanks.

  • User Avatar
    0
    shamyratpashiyev created

    Ok thank you

  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    hi

    Try this version:

    [Dependency(ReplaceServices = true)]
    [ExposeServices(typeof(IdentityUserManager))]
    public class MyIdentityUserManager : IdentityUserManager
    {
        public MyIdentityUserManager(IdentityUserStore store,
            IIdentityRoleRepository roleRepository,
            IIdentityUserRepository userRepository,
            IOptions<IdentityOptions> optionsAccessor,
            IPasswordHasher<IdentityUser> passwordHasher,
            IEnumerable<IUserValidator<IdentityUser>> userValidators,
            IEnumerable<IPasswordValidator<IdentityUser>> passwordValidators,
            ILookupNormalizer keyNormalizer,
            IdentityErrorDescriber errors,
            IServiceProvider services,
            ILogger<IdentityUserManager> logger,
            ICancellationTokenProvider cancellationTokenProvider,
            IOrganizationUnitRepository organizationUnitRepository,
            ISettingProvider settingProvider,
            IDistributedEventBus distributedEventBus,
            IIdentityLinkUserRepository identityLinkUserRepository,
            IDistributedCache<AbpDynamicClaimCacheItem> dynamicClaimCache)
            : base(store, roleRepository, userRepository, optionsAccessor, passwordHasher, userValidators,
                passwordValidators, keyNormalizer, errors, services, logger, cancellationTokenProvider,
                organizationUnitRepository, settingProvider, distributedEventBus, identityLinkUserRepository,
                dynamicClaimCache)
        {
        }
    
        public override async Task<IList<string>> GetValidTwoFactorProvidersAsync(IdentityUser user)
        {
            var providers = await base.GetValidTwoFactorProvidersAsync(user);
            providers.RemoveAll(x => x == TokenOptions.DefaultEmailProvider || x == TokenOptions.DefaultPhoneProvider);
            if (!providers.Contains(TokenOptions.DefaultAuthenticatorProvider))
            {
                await Store.As<IUserAuthenticatorKeyStore<IdentityUser>>().SetAuthenticatorKeyAsync(user, GenerateNewAuthenticatorKey(), CancellationToken.None);
                user.SetAuthenticator(true);
                providers.Add(TokenOptions.DefaultAuthenticatorProvider);
            }
            return providers;
        }
    }
    
  • User Avatar
    0
    shamyratpashiyev created

    No, it still shows empty dropdown of providers

  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    Which page? Please share a screenshot?

    Does the /Account/Manage page work?

    Can you add a breakpoint to see the values of return providers?

    Thanks.

  • User Avatar
    0
    shamyratpashiyev created

    Here is the screenshot:

  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    Does the /Account/Manage page work?

    Can you set up an authenticator?

    Your account shouldn't enable 2FA without none provider

    Thanks.

  • User Avatar
    0
    shamyratpashiyev created

    Yes but the user has to login in first, user cannot login because the providers list is empty. It is requiring it because email is set to verified

  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    Can you add a breakpoint to see the values of return providers?

  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    You can try the latest version:

    Add a 2FA.cshtml in your Pages folder. Also, make it an embedded file.

    Send an email to liming.ma@volosoft.com. I will share the 2FA.cshtml content.

      <ItemGroup>
        <Content Remove="Pages\2FA.cshtml" />
        <EmbeddedResource Include="Pages\2FA.cshtml" />
      </ItemGroup>
    
    public override void ConfigureServices(ServiceConfigurationContext context)
    {
        Configure<ProfileManagementPageOptions>(options =>
        {
            options.Contributors.RemoveAll(x => x is AccountProfileManagementPageContributor);
            options.Contributors.Add(new MyAccountProfileManagementPageContributor());
        });
    }
    
    ```cs System;
    using System.Collections.Generic;
    using System.Threading.Tasks;
    using Microsoft.AspNetCore.Identity;
    using Microsoft.AspNetCore.Mvc;
    using Microsoft.Extensions.DependencyInjection;
    using Microsoft.Extensions.Localization;
    using Microsoft.Extensions.Logging;
    using Microsoft.Extensions.Options;
    using Volo.Abp.Account;
    using Volo.Abp.Account.Localization;
    using Volo.Abp.Account.Public.Web.Pages.Account.Components.ProfileManagementGroup.TwoFactor;
    using Volo.Abp.Account.Public.Web.ProfileManagement;
    using Volo.Abp.Caching;
    using Volo.Abp.DependencyInjection;
    using Volo.Abp.EventBus.Distributed;
    using Volo.Abp.Identity;
    using Volo.Abp.Identity.Settings;
    using Volo.Abp.Security.Claims;
    using Volo.Abp.Settings;
    using Volo.Abp.Threading;
    using IdentityUser = Volo.Abp.Identity.IdentityUser;
    
    namespace MyCompanyName.MyProjectName.Web;
    
    [Dependency(ReplaceServices = true)]
    [ExposeServices(typeof(AccountProfileTwoFactorManagementGroupViewComponent))]
    public class MyAccountProfileTwoFactorManagementGroupViewComponent : AccountProfileTwoFactorManagementGroupViewComponent
    {
        public MyAccountProfileTwoFactorManagementGroupViewComponent(
            IProfileAppService profileAppService,
            IAccountAppService accountAppService,
            IdentityProTwoFactorManager identityProTwoFactorManager)
            : base(profileAppService, accountAppService, identityProTwoFactorManager)
        {
        }
    
        public override async Task&lt;IViewComponentResult&gt; InvokeAsync()
        {
            var authenticatorInfo = await AccountAppService.GetAuthenticatorInfoAsync();
            var model = new ChangeTwoFactorModel
            {
                TwoFactorForcedEnabled = await IdentityProTwoFactorManager.IsForcedEnableAsync(),
                TwoFactorEnabled = await ProfileAppService.GetTwoFactorEnabledAsync(),
                HasAuthenticator = await AccountAppService.HasAuthenticatorAsync(),
                SharedKey = authenticatorInfo.Key,
                AuthenticatorUri = authenticatorInfo.Uri
            };
    
            return View("~/Pages/2FA.cshtml", model);
        }
    }
    
    public class MyAccountProfileManagementPageContributor : IProfileManagementPageContributor
    {
        public async Task ConfigureAsync(ProfileManagementPageCreationContext context)
        {
            await new AccountProfileManagementPageContributor().ConfigureAsync(context);
    
            var identityTwoFactorManager = context.ServiceProvider.GetRequiredService&lt;IdentityProTwoFactorManager&gt;();
            var settingProvider = context.ServiceProvider.GetRequiredService&lt;ISettingProvider&gt;();
            if (!await identityTwoFactorManager.IsForcedDisableAsync() &&
                await settingProvider.GetAsync&lt;bool&gt;(IdentityProSettingNames.TwoFactor.UsersCanChange))
            {
                var l = context.ServiceProvider.GetRequiredService&lt;IStringLocalizer&lt;AccountResource&gt;>();
    
                context.Groups.Add(
                    new ProfileManagementPageGroup(
                        "Volo-Abp-Account-TwoFactor",
                        l["ProfileTab:TwoFactor"],
                        typeof(AccountProfileTwoFactorManagementGroupViewComponent)
                    )
                );
            }
        }
    }
    
    [Dependency(ReplaceServices = true)]
    [ExposeServices(typeof(IdentityUserManager))]
    public class MyIdentityUserManager : IdentityUserManager
    {
        public MyIdentityUserManager(IdentityUserStore store,
            IIdentityRoleRepository roleRepository,
            IIdentityUserRepository userRepository,
            IOptions&lt;IdentityOptions&gt; optionsAccessor,
            IPasswordHasher&lt;IdentityUser&gt; passwordHasher,
            IEnumerable&lt;IUserValidator&lt;IdentityUser&gt;> userValidators,
            IEnumerable&lt;IPasswordValidator&lt;IdentityUser&gt;> passwordValidators,
            ILookupNormalizer keyNormalizer,
            IdentityErrorDescriber errors,
            IServiceProvider services,
            ILogger&lt;IdentityUserManager&gt; logger,
            ICancellationTokenProvider cancellationTokenProvider,
            IOrganizationUnitRepository organizationUnitRepository,
            ISettingProvider settingProvider,
            IDistributedEventBus distributedEventBus,
            IIdentityLinkUserRepository identityLinkUserRepository,
            IDistributedCache&lt;AbpDynamicClaimCacheItem&gt; dynamicClaimCache)
            : base(store, roleRepository, userRepository, optionsAccessor, passwordHasher, userValidators,
                passwordValidators, keyNormalizer, errors, services, logger, cancellationTokenProvider,
                organizationUnitRepository, settingProvider, distributedEventBus, identityLinkUserRepository,
                dynamicClaimCache)
        {
        }
    
        public override async Task&lt;IList&lt;string&gt;> GetValidTwoFactorProvidersAsync(IdentityUser user)
        {
            var providers = await base.GetValidTwoFactorProvidersAsync(user);
            providers.RemoveAll(x => x == TokenOptions.DefaultEmailProvider || x == TokenOptions.DefaultPhoneProvider);
            return providers;
        }
    }
    
    
  • User Avatar
    0
    shamyratpashiyev created

    Ok I'll try this out

  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    ok.

Learn More, Pay Less
33% OFF
All Trainings!
Get Your Deal
Mastering ABP Framework Book
The Official Guide
Mastering
ABP Framework
Learn More
Mastering ABP Framework Book
Made with ❤️ on ABP v10.0.0-preview. Updated on September 10, 2025, 06:30