9 Answer(s)
-
0
- This is a known Angular UI issue (settings are saved, but the password validation UI/behavior doesn’t reflect the updated policy correctly in some versions).
- Fix: upgrade the Identity config package used by the Angular UI, then reinstall node modules to ensure the new package is actually applied.
Steps:
- In your Angular project folder, run:
yarn upgrade @volo/abp.ng.identity.config- Delete your lock file and reinstall packages:
- remove
yarn.lock(orpackage-lock.json) - then run:
yarn install- Rebuild and restart the Angular app.
Sources:
- https://abp.io/support/questions/166
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) -
0
Hi,
The screenshot alone isn't enough for us to tell what's going wrong. Could you share a bit more so we can reproduce it:
- ABP version, UI type (Angular / MVC / Blazor) and whether the solution is tiered (separated auth server).
- What exactly does "not working" look like — does Save fail with an error, or does it save successfully but the new policy doesn't take effect when a user actually changes their password?
- Are you updating the host settings or a tenant's settings? Does the same happen on the other side?
- If Save fails, the browser DevTools → Network response for the settings update request, plus any related server-side log.
- If Save succeeds but the policy isn't applied, the exact steps you take afterwards (e.g. change password from My Account, create a user from admin UI) and what message you see.
If the logs / repro are too large for the thread, feel free to send them to
liming.ma@volosoft.com.Thanks
Markdown supported.Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post) -
0Markdown supported.Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post)
-
0Markdown supported.Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post)
-
0Markdown supported.Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post)
-
0
Hi,
Thanks for the DB query — the rows look correct (host writes
T + NULL, tenants writeT + <tenantId>), so the value 8 is saved. The screenshot still showing 6 looks like a different code path or stale state at validation time.Could you share:
- Which user are you logged in as when you hit Change Password — host admin, or a tenant user?
- The URL of the Change Password page in the screenshot —
/account/change-password,/Account/ChangePassword, or somewhere else? - Did you do a full browser refresh (F5) between saving the setting and opening Change Password?
- Server-side debug log for the failing change-password request — enable
Volo.Abp.SettingsandVolo.Abp.Identityat Debug level following https://abp.io/support/questions/8622/How-to-enable-Debug-logs-for-troubleshoot-problems. The log will show the actualRequiredLengthvalue used at validation time. - Any customizations on
IdentitySettingsAppService,SettingProvider, the Change Password page orProfileAppService.ChangePasswordAsync?
Feel free to send logs to
liming.ma@volosoft.comif too large for the thread.Thanks
Markdown supported.Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post) -
0
Hi,
One more question — are you able to reproduce this in a fresh ABP template project (same version, Angular, Pro), or does it only happen in your PRIM project? If you can reproduce in a clean template, please share the exact steps; if not, it likely points to something specific in your project (custom services, modules, or middleware) and we can narrow down from there.
Thanks
Markdown supported.Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post) -
0
I have tried to reproduce the issue in New fresh Abp Template project I did not found any issue. I have done the customizations Change password Screen I am adding Latest code of Change password model Please verify does it need any change
using Hon.IFS.SiteHost; using Hon.IFS.SiteHost.EmailNotifications; using Hon.IFS.SiteHost.Pages.Account; using Hon.IFS.SiteHost.StringEncryption; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using Serilog.Core; using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Linq; using System.Reflection; using System.Threading.Tasks; using Volo.Abp.Account.Public.Web.Pages.Account; using Volo.Abp.Auditing; using Volo.Abp.Identity; using Volo.Abp.Security.Encryption; using Volo.Abp.Validation; using Volo.Saas.Tenants; using static Volo.Abp.Identity.Settings.IdentitySettingNames; using static Volo.Saas.Host.SaasHostPermissions; using IdentityUser = Volo.Abp.Identity.IdentityUser;
namespace Volo.Abp.Account.Web.Pages.Account;
public class ChangePasswordModel : AccountPageModel { private readonly SignInManager<IdentityUser> _signInManager; private readonly UserManager<IdentityUser> _userManager; public IIdentityUserRepository _identityUserRepository; private readonly IConfiguration _configuration; private readonly IEmailNotificationAppService? _emailService; private readonly ITenantRepository _tenantRepository; private readonly ILogger<ChangePasswordModel> _logger;
[BindProperty] [Required(ErrorMessage = "Current password is required.")] [DataType(DataType.Password)] public string CurrentPassword { get; set; } = null!; [BindProperty] [Required(ErrorMessage = "New password is required.")] [DataType(DataType.Password)] [DynamicStringLength(typeof(IdentityUserConsts), nameof(IdentityUserConsts.MaxPasswordLength))] public string NewPassword { get; set; } = null!; [Required] [HiddenInput] [BindProperty(SupportsGet = true)] public Guid UserId { get; set; } [Required] [HiddenInput] [BindProperty(SupportsGet = true)] public string TenantId { get; set; } = null!; [BindProperty] [Required(ErrorMessage = "Confirm new password is required.")] [DataType(DataType.Password)] [Compare("NewPassword", ErrorMessage = "Passwords do not match.")] [DynamicStringLength(typeof(IdentityUserConsts), nameof(IdentityUserConsts.MaxPasswordLength))] public string ConfirmNewPassword { get; set; } = null!; [HiddenInput] [BindProperty(SupportsGet = true)] public string ReturnUrl { get; set; } = null!; [HiddenInput] [BindProperty(SupportsGet = true)] public string ReturnUrlHash { get; set; } = null!; [BindProperty(SupportsGet = false)] public string? TimezoneId { get; set; } public ChangePasswordModel( UserManager<IdentityUser> userManager, SignInManager<IdentityUser> signInManager, IIdentityUserRepository identityUserRepository, IConfiguration configuration, ITenantRepository tenantRepository, IEmailNotificationAppService emailService, ILogger<ChangePasswordModel> logger) { _userManager = userManager; _signInManager = signInManager; _identityUserRepository = identityUserRepository; _configuration = configuration; _tenantRepository = tenantRepository; _emailService = emailService; _logger = logger; } public IActionResult OnGet([FromQuery] Guid userId, [FromQuery] Guid tenantId) { UserId = userId; return Page(); } public async Task<IActionResult> OnPostAsync() { using (CurrentTenant.Change(new Guid(TenantId))) { var user = await _identityUserRepository.FindAsync(UserId); if (user == null) { throw new AbpException("User not found."); } if (string.IsNullOrWhiteSpace(NewPassword)) { ModelState.AddModelError(nameof(NewPassword), "Password cannot be empty."); return Page(); } if (string.IsNullOrWhiteSpace(ConfirmNewPassword)) { ModelState.AddModelError(nameof(ConfirmNewPassword), "Confirm password cannot be empty."); return Page(); } if (!string.IsNullOrWhiteSpace(NewPassword) && !string.IsNullOrWhiteSpace(ConfirmNewPassword)) { if (NewPassword.Trim() != ConfirmNewPassword.Trim()) { ModelState.AddModelError( nameof(ConfirmNewPassword), L["Password and Confirm password do not match."] ); return Page(); } } var changePasswordResult = await _userManager.ChangePasswordAsync(user, CurrentPassword, NewPassword); // Only proceed if password change was successful if (changePasswordResult.Succeeded) { _logger.LogInformation("Password successfully changed for user {Username} (ID: {UserId})", user.UserName, user.Id); // Update user to not require password change on next login user.SetShouldChangePasswordOnNextLogin(false); await UserManager.UpdateAsync(user); // Get tenant information for email var tenant = await _tenantRepository.FindAsync(new Guid(TenantId)); if (tenant == null) { _logger.LogWarning("Tenant not found for TenantId: {TenantId}", TenantId); return BadRequest("Tenant not found"); } var tenantHostName = tenant.ExtraProperties .FirstOrDefault(kvp => kvp.Key.Equals("tenantHostName", StringComparison.OrdinalIgnoreCase)) .Value?.ToString(); // Send confirmation email after successful password change _logger.LogInformation("Preparing to send password change confirmation email to {Email} for user {Username}", user.Email, user.UserName); var emailModel = new EmailNotificationDto { MailTo = user.Email, MailSubject = EmailNotificationConstants.EmailTypes.ChangePassword, IsBodyHtml = true, Username = user.UserName, Name = user.Name, TenantName = tenant.Name, TimezoneId = TimezoneId // Use timezone from client }; try { if (_emailService == null) { _logger.LogWarning("Email service is not available. Skipping email notification."); } else { _logger.LogDebug("Calling email service to send notification..."); var sendTask = _emailService.EmailNotification(emailModel); if (await Task.WhenAny(sendTask, Task.Delay(TimeSpan.FromSeconds(30))) == sendTask) { await sendTask; _logger.LogInformation("Password change email successfully sent to {Email}", user.Email); } else { _logger.LogWarning("Email sending timed out after 30 seconds for {Email}", user.Email); } } } catch (Exception ex) { _logger.LogError(ex, "Failed to send password change email to {Email}. Error: {ErrorMessage}", user.Email, ex.Message); } // Refresh the sign-in await _signInManager.RefreshSignInAsync(user); return RedirectToPage("/Account/Login", new { tenantName = tenantHostName, returnUrl = ReturnUrl, returnUrlHash = ReturnUrlHash }); } // Password change failed - add errors to ModelState _logger.LogWarning("Password change failed for user {Username} (ID: {UserId})", user.UserName, user.Id); foreach (var error in changePasswordResult.Errors) { _logger.LogDebug("Password change error: {ErrorCode} - {ErrorDescription}", error.Code, error.Description); ModelState.AddModelError(string.Empty, error.Description); } return Page(); } }}
Markdown supported.Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post) -
0
Hi,
Found it. Your
OnPostAsynccalls_userManager.ChangePasswordAsync(...)without callingIdentityOptions.SetAsync()first, so ASP.NET Core Identity validates the password against the framework defaultPasswordOptions(length=6, requires digit/lower/upper/non-alphanumeric) instead of the values fromAbpSettings. That's exactly why the page shows the old "6 characters" errors no matter what you save.IdentityOptions.SetAsync()reads the currentAbp.Identity.Password.*settings (respecting tenant scope) and pushes them intoIdentityOptions.Password, soUserManager.ChangePasswordAsyncvalidates against the values you saved. InjectIOptions<IdentityOptions>and callSetAsync()inside theCurrentTenant.Changescope, beforeChangePasswordAsync:private readonly IOptions<IdentityOptions> _identityOptions; public ChangePasswordModel( UserManager<IdentityUser> userManager, SignInManager<IdentityUser> signInManager, IIdentityUserRepository identityUserRepository, IConfiguration configuration, ITenantRepository tenantRepository, IEmailNotificationAppService emailService, ILogger<ChangePasswordModel> logger, IOptions<IdentityOptions> identityOptions) { _userManager = userManager; _signInManager = signInManager; _identityUserRepository = identityUserRepository; _configuration = configuration; _tenantRepository = tenantRepository; _emailService = emailService; _logger = logger; _identityOptions = identityOptions; } public async Task<IActionResult> OnPostAsync() { using (CurrentTenant.Change(new Guid(TenantId))) { await _identityOptions.SetAsync(); // <-- add this var user = await _identityUserRepository.FindAsync(UserId); ... var changePasswordResult = await _userManager.ChangePasswordAsync(user, CurrentPassword, NewPassword); ... } }One unrelated note while we're looking at this page:
OnGet/OnPostbindUserIdandTenantIdfrom query string with no ownership check, so a logged-in user could change any other user's password by editing the URL. You'll likely want to derive them from the current cookie / principal instead, the way ABP's built-inChangePasswordpage does.Thanks
Markdown supported.Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post)




