spring-projects/spring-security · error · BadCredentialsException

Authentication for password change failed.

Error message

Authentication for password change failed.

What it means

LdapUserDetailsManager.changePassword() with usePasswordModifyExtensionOperation=false performs the change by reconnecting the context with the old credentials and then modifying the password attribute. If the reconnect raises javax.naming.AuthenticationException, the old password was wrong, and changePasswordUsingAttributeModification throws BadCredentialsException('Authentication for password change failed.').

Solutions

  1. Have the user re-enter and confirm their current (old) password — it must authenticate successfully first.
  2. Verify the context's SECURITY_PRINCIPAL is the user's own DN, not a shared/admin identity with different credentials.
  3. Catch BadCredentialsException in the change-password flow and prompt for the current password again.
  4. If policy complexities exist, consider enabling the password modify extended operation (setPasswordModifyExtensionOperation(true)).

Example fix

// before
manager.changePassword(oldPassword, newPassword); // opaque failure
// after
try {
    manager.changePassword(oldPassword, newPassword);
}
catch (BadCredentialsException e) {
    bindingResult.rejectValue("oldPassword", "wrong.current.password");
    return "password/change";
}
Defensive patterns

Strategy: try-catch

Validate before calling

// pre-check old password with a bind attempt
try (DirContext c = contextSource.getContext(userDn, oldPassword)) {
    // old password valid; proceed to changePassword
}

Try / catch

try {
    ldapUserDetailsManager.changePassword(oldPassword, newPassword);
} catch (BadCredentialsException e) {
    bindingResult.rejectValue("oldPassword", "wrong.current.password");
}

Prevention

When it happens

Trigger: Calling changePassword(oldPassword, newPassword) where reconnect(null) with SECURITY_CREDENTIALS=oldPassword fails with javax.naming.AuthenticationException — i.e. the supplied old password does not authenticate against the directory.

Common situations: User typed their current password incorrectly on a change-password form; session was re-bound with admin credentials so the reconnect uses the wrong identity; directory rejects the bind due to policy (locked/expired) mid-change.

Understand the failure class

Related errors


AI-assisted analysis of spring-projects/spring-security@96852e8860 (2026-09-10). Data as JSON: /api/errors/49093c2eca7e6b66. Report an issue: GitHub.

Appendix: source

Thrown at ldap/src/main/java/org/springframework/security/ldap/userdetails/LdapUserDetailsManager.java:453

	private void changePasswordUsingAttributeModification(LdapName userDn, @Nullable String oldPassword,
			@Nullable String newPassword) {
		ModificationItem[] passwordChange = new ModificationItem[] { new ModificationItem(DirContext.REPLACE_ATTRIBUTE,
				new BasicAttribute(this.passwordAttributeName, newPassword)) };
		if (oldPassword == null) {
			this.template.modifyAttributes(userDn, passwordChange);
			return;
		}
		this.template.executeReadWrite((dirCtx) -> {
			LdapContext ctx = (LdapContext) dirCtx;
			ctx.removeFromEnvironment("com.sun.jndi.ldap.connect.pool");
			ctx.addToEnvironment(Context.SECURITY_PRINCIPAL, LdapUtils.getFullDn(userDn, ctx).toString());
			ctx.addToEnvironment(Context.SECURITY_CREDENTIALS, oldPassword);
			// TODO: reconnect doesn't appear to actually change the credentials
			try {
				ctx.reconnect(null);
			}
			catch (javax.naming.AuthenticationException ex) {
				throw new BadCredentialsException("Authentication for password change failed.");
			}
			ctx.modifyAttributes(userDn, passwordChange);
			return void.class;
		});
	}

	private void changePasswordUsingExtensionOperation(LdapName userDn, @Nullable String oldPassword,
			@Nullable String newPassword) {
		this.template.executeReadWrite((dirCtx) -> {
			LdapContext ctx = (LdapContext) dirCtx;
			String userIdentity = LdapUtils.getFullDn(userDn, ctx).toString();
			PasswordModifyRequest request = new PasswordModifyRequest(userIdentity, oldPassword, newPassword);
			try {
				return ctx.extendedOperation(request);
			}
			catch (javax.naming.AuthenticationException ex) {
				throw new BadCredentialsException("Authentication for password change failed.");
			}

View on GitHub (pinned to 96852e8860)