Open Closed

Password change for Tenant User via Host Admin UI does not persist #10595


User avatar
0
chrisalves created

Environment Details

  • ABP Studio Version: 2.2.6
  • UI Framework: Blazor Server
  • Architecture: Tiered
  • Identity Server: OpenIddict
  • Database: SQL Server (Separate Tenant Schema)
  • Distributed Event Bus: RabbitMQ
  • Template: app
  • Created ABP Studio Version: 1.0.2
  • Current ABP Studio Version: 2.2.6
  • Tiered: Yes
  • Multi-Tenancy: Yes
  • UI Framework: blazor-server
  • Theme: leptonx
  • Theme Style: system
  • Theme Menu Placement: side
  • Database Provider: ef
  • Database Management System: sqlserver
  • Separate Tenant Schema: Yes
  • Create Initial Migration: Yes
  • Run Db Migrator: Yes
  • Mobile Framework: maui
  • Public Website: Yes
  • Social Login: Yes
  • Include Tests: Yes
  • Kubernetes Configuration: No
  • Distributed Event Bus: rabbitmq
  • Use Local References: No
  • Optional Modules:
    • GDPR
    • FileManagement
    • TextTemplateManagement
    • LanguageManagement
    • AuditLogging
    • Chat
    • OpenIddictAdmin
  • Selected Languages: English, English (United Kingdom), 简体中文, Español, العربية, हिन्दी, Português (Brasil), Français, Русский, Deutsch (Deuthschland), Türkçe, Italiano, Čeština, Magyar, Română (România), Svenska, Suomi, Slovenčina, Íslenska, 繁體中文
  • Default Language: English
  • Create Command: abp new NewApp -t app --tiered --ui-framework blazor-server --mobile maui --database-provider ef --database-management-system sqlserver --theme leptonx --separate-tenant-schema --public-website --without-cms-kit --dont-run-bundling -chat -file-management

Ticket Description

Subject: Password change for Tenant User via Host Admin UI does not persist or sync with AuthServer


Description: When changing a Tenant's user password through the Host Admin UI, the system returns a success notification, but the authentication process still requires the old password.


Steps to Reproduce (as shown in attached images)

  1. Created a new tenant with Password_A.

  2. Logged in as Host Admin, navigated to the Tenant's user management, and updated the password to Password_B. A success toast message was displayed.

  3. Attempted to log in using the new Password_B. The system returned an "Invalid username or password" error.

  4. Attempted to log in using the old Password_A. The login was successful, proving the update was not effectively persisted in the authentication layer.


Expected Behavior

The old password should be invalidated immediately, and the new password should be required for all subsequent login attempts.

Markdown supported.
Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post)

7 Answer(s)
  • User Avatar
    0
    AI-Bot created
    Support Team ABP Agent
    • In a tiered + separate AuthServer solution, the login password is validated by the AuthServer’s Identity database, not by the Blazor (UI) or HttpApi.Host database.
    • With Separate Tenant Schema, it’s possible to end up changing the password in a different database/schema than the one the AuthServer is actually using for that tenant (or changing it under the wrong tenant context). The UI can still show “Saved successfully” because the SaaS operation succeeded, but the AuthServer still has the old hash, so Password_A keeps working.

    Fix (recommended): ensure the password is set in the AuthServer’s Identity DB under the target tenant context

    1. In your Host Admin UI, when you “Set password” for a tenant user, call an endpoint that runs in the AuthServer (or a shared application contract implemented in AuthServer), not only in the UI/host side.
    2. In that endpoint, wrap the operation with the correct tenant:
    using (CurrentTenant.Change(targetTenantId))
       {
           // set password for the tenant's admin/user via IdentityUserManager/UserAppService
           // (this ensures it updates the tenant's identity store that AuthServer uses)
       }
    
    1. If you are using a SaaS tenant password operation (like “Set password” on tenant), replace/override the relevant SaaS app service and ensure it validates/sets the password in the correct context. ABP support confirms SaaS and Identity are separate concerns and customization is required in such scenarios; example replacement is shown in the verified ticket below.

    Verification steps

    • After applying the fix, directly check the AuthServer database for the tenant’s AbpUsers.PasswordHash (in the tenant schema) and confirm it changes when you set the password from Host.
    • Then login should immediately require Password_B.


    Sources (verified)

    • https://abp.io/support/questions/9002
    • https://abp.io/docs/latest/modules/saas

    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.

    Markdown supported.
    Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post)
  • User Avatar
    0
    chrisalves created

    Hi,

    I am requesting a review from a human support agent, as the AI-generated response suggests a manual fix/workaround for what appears to be a fundamental bug in the standard ABP Studio template.

    To clarify:

    I have no customizations: This is a clean, unmodified application generated directly via ABP Studio (version 1.0.2).

    Standard Feature Failure: The "Set Password" functionality for Tenant Users via the Host UI is a built-in feature of the SaaS/Identity modules.

    Best regards.

    Markdown supported.
    Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post)
  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    Hi,

    The "Set Password" from the Tenant management page works through a distributed event internally — the UI shows success as soon as the event is published, but the actual password change happens asynchronously in a background handler. So the success toast doesn't necessarily mean the password was actually changed.

    Could you check the logs of your HttpApi.Host and AuthServer after attempting the password change? Look for any of these messages:

    • User password changed: admin — means it actually worked
    • User password change failed: admin — password validation error
    • An error occurred while handling UserPasswordChangeRequestedEto — exception during processing
    • If none of these appear, the event was never processed at all

    You can share the relevant logs(Logs.txt) to liming.ma@volosoft.com if that's easier.

    Thanks

    Markdown supported.
    Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post)
  • User Avatar
    0
    chrisalves created

    Hi @maliming,

    Thanks for the clarification regarding the distributed event background process. I investigated the application logs as you suggested and found the root cause of the issue.

    Findings

    There is a password complexity validation enforcing in the background when trying to change the password. This validation is enforcing the use of non-alphanumeric characters. However, I realized several inconsistencies in how the standard ABP platform handles this:

    1. Missing UI Feedback

    The front-end does not display the result of this validation. It simply shows a success toast because the event was published, misleading the user.

    2. Configuration Ignored

    As shown in the bellow attached image, the current tenant configuration does not require non-alphanumeric characters (it only requires a minimum of 6 characters). The system should accept a simple password based on these settings.

    3. Inconsistent Validation (Creation vs. Update)

    When creating a brand new tenant, the system does not enforce a complex password for the new admin. It creates the user without any restrictions. The strict validation only occurs when the Host Admin tries to change a Tenant Admin password later.

    Tests Performed

    To validate this behavior, I ran the following tests:

    • Test 1 (Failed): Using the password CafeForte456 (no special characters). The log shows a failure, and the password is not changed.

    • Test 2 (Success): Using the password CafeForte@456 (with a special character). The password updates successfully.

    It seems to be a bug in the business rules enforcing a condition that is not required in the Tenant settings.

    Looking forward to your feedback.

    Best regards.

    Markdown supported.
    Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post)
  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    Hi,

    Thank you so much for the detailed investigation, this is very helpful. You're absolutely right — I've confirmed this is a bug in our Pro UserPasswordChangeRequestedEventHandler.

    The root cause is that this handler does not call await IdentityOptions.SetAsync(); before validating the password. SetAsync() is what loads the tenant-specific identity settings (password policy, lockout, etc.) into the IdentityOptions instance. Without it, the handler falls back to ASP.NET Core Identity's default policy, which requires digit, uppercase, lowercase and non-alphanumeric characters — exactly what you observed. Other places like IdentityUserAppService.UpdatePasswordAsync do call SetAsync(), so only this distributed event handler is affected.

    We'll fix it in the next version by adding the SetAsync() call at the beginning of HandleEventAsync.

    Temporary workaround

    You can replace the buggy handler in your *.Domain project by doing two things:

    1. Add a replacement handler class in your *.Domain project:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Threading.Tasks;
    using Microsoft.AspNetCore.Identity;
    using Microsoft.Extensions.Logging;
    using Microsoft.Extensions.Logging.Abstractions;
    using Microsoft.Extensions.Options;
    using Volo.Abp.DependencyInjection;
    using Volo.Abp.EventBus.Distributed;
    using Volo.Abp.Identity;
    using Volo.Abp.Users;
    
    namespace MyCompanyName.MyProjectName.Identity;
    
    public class MyUserPasswordChangeRequestedEventHandler :
        IDistributedEventHandler<UserPasswordChangeRequestedEto>,
        ITransientDependency
    {
        public ILogger<MyUserPasswordChangeRequestedEventHandler> Logger { get; set; }
    
        protected IIdentityUserRepository UserRepository { get; }
        protected IdentityUserManager IdentityUserManager { get; }
        protected IOptions<IdentityOptions> IdentityOptions { get; }
    
        public MyUserPasswordChangeRequestedEventHandler(
            IIdentityUserRepository userRepository,
            IdentityUserManager identityUserManager,
            IOptions<IdentityOptions> identityOptions)
        {
            Logger = NullLogger<MyUserPasswordChangeRequestedEventHandler>.Instance;
            UserRepository = userRepository;
            IdentityUserManager = identityUserManager;
            IdentityOptions = identityOptions;
        }
    
        public async Task HandleEventAsync(UserPasswordChangeRequestedEto eventData)
        {
            try
            {
                await IdentityOptions.SetAsync();
    
                if (!eventData.Password.IsNullOrEmpty())
                {
                    var user = await UserRepository.FindByTenantIdAndUserNameAsync(eventData.UserName, eventData.TenantId);
                    if (user != null)
                    {
                        var errors = await ValidatePasswordAsync(user, eventData.Password);
                        if (errors.Any())
                        {
                            Logger.LogError("User password change failed: {userName}, reason: {reason}", eventData.UserName,
                                string.Join(";", errors.Select(e => e.Code)));
                        }
                        else
                        {
                            (await IdentityUserManager.RemovePasswordAsync(user)).CheckErrors();
                            (await IdentityUserManager.AddPasswordAsync(user, eventData.Password)).CheckErrors();
                            Logger.LogInformation("User password changed: {userName}", eventData.UserName);
                        }
                    }
                }
            }
            catch (Exception e)
            {
                Logger.LogError("An error occurred while handling UserPasswordChangeRequestedEto: {message}", e.Message);
                Logger.LogException(e);
            }
        }
    
        private async Task<List<IdentityError>> ValidatePasswordAsync(IdentityUser user, string password)
        {
            var errors = new List<IdentityError>();
            foreach (var v in IdentityUserManager.PasswordValidators)
            {
                var result = await v.ValidateAsync(IdentityUserManager, user, password);
                if (!result.Succeeded && result.Errors.Any())
                {
                    errors.AddRange(result.Errors);
                }
            }
            return errors;
        }
    }
    

    2. Unsubscribe the original handler in your *.Domain module's ConfigureServices:

    public override void ConfigureServices(ServiceConfigurationContext context)
    {
        Configure<AbpDistributedEventBusOptions>(options =>
        {
            options.Handlers.Remove<UserPasswordChangeRequestedEventHandler>();
        });
    
        // ... your other configurations
    }
    

    The Remove call runs after AbpEventBusModule has populated the handlers list, so only your new handler will be subscribed to UserPasswordChangeRequestedEto. After this, the Set Password action from the Tenant management page will respect your tenant's password policy and actually change the password.

    Regarding your other two points:

    • Missing UI feedback: you're right, it's a limitation of the fire-and-forget distributed event pattern used by Set Password. The UI can't tell whether the background handler succeeded. We'll review this separately.
    • Inconsistent validation on tenant creation: the initial admin user is created through the data seeder which doesn't run PasswordValidators, which is why a simple password is accepted at creation time. We'll revisit this to make the behavior more consistent.

    Thanks again for the great bug report! Your ticket has been refunded. 👍

    Markdown supported.
    Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post)
  • User Avatar
    0
    chrisalves created

    Hi @maliming,

    Thank you for the clarification on this issue — I really appreciate your assistance.

    We’ve decided not to change anything in the code for now and will wait for the bug to be fixed in a future release. In the meantime, we are implementing an internal procedure to address the password‑complexity requirement, even though this setting is disabled in the tenant configuration. Your well‑documented workaround may still be helpful for others who choose to apply it in their environments.

    Have a great day!

    Markdown supported.
    Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post)
  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    Hi,

    Thanks for the update, glad to hear you have an internal procedure in place for now. The fix is already merged and will ship in the next release, so you can drop the workaround once you upgrade.

    Thanks

    Markdown supported.
    Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post)
Boost Your Development
ABP Live Training
Packages
See Trainings
Mastering ABP Framework Book
The Official Guide
Mastering
ABP Framework
Learn More
Mastering ABP Framework Book
Made with ❤️ on ABP v10.8.0-preview. Updated on September 16, 2026, 14:50
1
ABP Assistant
🔐 You need to be logged in to use the chatbot. Please log in first.