{"record":{"id":"49093c2eca7e6b66","repo":"spring-projects/spring-security","slug":"authentication-for-password-change-failed","errorCode":null,"errorMessage":"Authentication for password change failed.","messagePattern":"Authentication for password change failed\\.","errorType":"exception","errorClass":"BadCredentialsException","httpStatus":null,"severity":"error","filePath":"ldap/src/main/java/org/springframework/security/ldap/userdetails/LdapUserDetailsManager.java","lineNumber":453,"sourceCode":"\tprivate void changePasswordUsingAttributeModification(LdapName userDn, @Nullable String oldPassword,\n\t\t\t@Nullable String newPassword) {\n\t\tModificationItem[] passwordChange = new ModificationItem[] { new ModificationItem(DirContext.REPLACE_ATTRIBUTE,\n\t\t\t\tnew BasicAttribute(this.passwordAttributeName, newPassword)) };\n\t\tif (oldPassword == null) {\n\t\t\tthis.template.modifyAttributes(userDn, passwordChange);\n\t\t\treturn;\n\t\t}\n\t\tthis.template.executeReadWrite((dirCtx) -> {\n\t\t\tLdapContext ctx = (LdapContext) dirCtx;\n\t\t\tctx.removeFromEnvironment(\"com.sun.jndi.ldap.connect.pool\");\n\t\t\tctx.addToEnvironment(Context.SECURITY_PRINCIPAL, LdapUtils.getFullDn(userDn, ctx).toString());\n\t\t\tctx.addToEnvironment(Context.SECURITY_CREDENTIALS, oldPassword);\n\t\t\t// TODO: reconnect doesn't appear to actually change the credentials\n\t\t\ttry {\n\t\t\t\tctx.reconnect(null);\n\t\t\t}\n\t\t\tcatch (javax.naming.AuthenticationException ex) {\n\t\t\t\tthrow new BadCredentialsException(\"Authentication for password change failed.\");\n\t\t\t}\n\t\t\tctx.modifyAttributes(userDn, passwordChange);\n\t\t\treturn void.class;\n\t\t});\n\t}\n\n\tprivate void changePasswordUsingExtensionOperation(LdapName userDn, @Nullable String oldPassword,\n\t\t\t@Nullable String newPassword) {\n\t\tthis.template.executeReadWrite((dirCtx) -> {\n\t\t\tLdapContext ctx = (LdapContext) dirCtx;\n\t\t\tString userIdentity = LdapUtils.getFullDn(userDn, ctx).toString();\n\t\t\tPasswordModifyRequest request = new PasswordModifyRequest(userIdentity, oldPassword, newPassword);\n\t\t\ttry {\n\t\t\t\treturn ctx.extendedOperation(request);\n\t\t\t}\n\t\t\tcatch (javax.naming.AuthenticationException ex) {\n\t\t\t\tthrow new BadCredentialsException(\"Authentication for password change failed.\");\n\t\t\t}","sourceCodeStart":435,"sourceCodeEnd":471,"githubUrl":"https://github.com/spring-projects/spring-security/blob/96852e8860138a482cb13d1479573f24ff6443c6/ldap/src/main/java/org/springframework/security/ldap/userdetails/LdapUserDetailsManager.java#L435-L471","documentation":"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.').","triggerScenarios":"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.","commonSituations":"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.","solutions":["Have the user re-enter and confirm their current (old) password — it must authenticate successfully first.","Verify the context's SECURITY_PRINCIPAL is the user's own DN, not a shared/admin identity with different credentials.","Catch BadCredentialsException in the change-password flow and prompt for the current password again.","If policy complexities exist, consider enabling the password modify extended operation (setPasswordModifyExtensionOperation(true))."],"exampleFix":"// before\nmanager.changePassword(oldPassword, newPassword); // opaque failure\n// after\ntry {\n    manager.changePassword(oldPassword, newPassword);\n}\ncatch (BadCredentialsException e) {\n    bindingResult.rejectValue(\"oldPassword\", \"wrong.current.password\");\n    return \"password/change\";\n}","handlingStrategy":"try-catch","validationCode":"// pre-check old password with a bind attempt\ntry (DirContext c = contextSource.getContext(userDn, oldPassword)) {\n    // old password valid; proceed to changePassword\n}","typeGuard":null,"tryCatchPattern":"try {\n    ldapUserDetailsManager.changePassword(oldPassword, newPassword);\n} catch (BadCredentialsException e) {\n    bindingResult.rejectValue(\"oldPassword\", \"wrong.current.password\");\n}","preventionTips":["Confirm the current password with a bind test before the change flow.","Ensure SECURITY_PRINCIPAL is the user's own DN, not a shared identity.","Catch BadCredentialsException explicitly in change-password forms.","Consider RFC 3062 extended operation for directories where reconnect semantics are flaky."],"tags":["ldap","password-change","bad-credentials","spring-security"],"backgroundTag":"missing-credentials","analyzedSha":"96852e8860138a482cb13d1479573f24ff6443c6","analyzedAt":"2026-09-10T23:25:23.477Z","contentChangedAt":"2026-09-10T23:25:23.477Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}