Greetings, there are some problems I encountered in layered ABP solution:
- 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.
- 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)
-
0
- 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.
- 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.
-
0
No I tried that, didn't work
-
0
hi
You can remove the email and phone from
options.Tokens.ProviderMap
services.PostConfigure<IdentityOptions>(options => { //options.Tokens.ProviderMap });
-
0
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.
-
0
-
0
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.
-
0
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
-
0
You don't need to override the cshtml page. Just need the
SendSecurityCodeModel
Thanks.
-
0
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?
-
0
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 ofIdentityUserManager
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; } }
-
0
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
-
0
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?
-
0
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.
-
0
hi
I will check and provide a solution.
Thanks.
-
0
Ok thank you
-
0
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; } }
-
0
No, it still shows empty dropdown of providers
-
0
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.
-
0
-
0
Does the
/Account/Manage
page work?Can you set up an authenticator?
Your account shouldn't enable 2FA without none provider
Thanks.
-
0
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
-
0
-
0
You can try the latest version:
Add a
2FA.cshtml
in yourPages
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<IViewComponentResult> 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<IdentityProTwoFactorManager>(); var settingProvider = context.ServiceProvider.GetRequiredService<ISettingProvider>(); if (!await identityTwoFactorManager.IsForcedDisableAsync() && await settingProvider.GetAsync<bool>(IdentityProSettingNames.TwoFactor.UsersCanChange)) { var l = context.ServiceProvider.GetRequiredService<IStringLocalizer<AccountResource>>(); 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<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; } }
-
0
Ok I'll try this out
-
0
ok.