Index: oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/UserAuthentication.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/UserAuthentication.java	(revision 1873068)
+++ oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/UserAuthentication.java	(date 1579795776000)
@@ -142,6 +142,8 @@
             principal = user.getPrincipal();
         } catch (RepositoryException e) {
             throw new LoginException(e.getMessage());
+        } finally {
+            removeNewPwAttribute(credentials);
         }
         return success;
     }
@@ -204,6 +206,17 @@
         return false;
     }
 
+    /**
+     * Make sure the new-password attribute is no longer present on the credentials after the login phase
+     *
+     * @param credentials
+     */
+    private static void removeNewPwAttribute(@NotNull Credentials credentials) {
+        if (credentials instanceof SimpleCredentials) {
+            ((SimpleCredentials) credentials).removeAttribute(CREDENTIALS_ATTRIBUTE_NEWPASSWORD);
+        }
+    }
+
     private boolean impersonate(AuthInfo info, User user) {
         try {
             if (user.getID().equals(info.getUserID())) {
Index: oak-core/src/test/java/org/apache/jackrabbit/oak/security/user/PasswordExpiryAndForceInitialChangeTest.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- oak-core/src/test/java/org/apache/jackrabbit/oak/security/user/PasswordExpiryAndForceInitialChangeTest.java	(revision 1873068)
+++ oak-core/src/test/java/org/apache/jackrabbit/oak/security/user/PasswordExpiryAndForceInitialChangeTest.java	(date 1579795828000)
@@ -32,7 +32,9 @@
 import org.junit.Before;
 import org.junit.Test;
 
+import static org.apache.jackrabbit.oak.spi.security.user.UserConstants.CREDENTIALS_ATTRIBUTE_NEWPASSWORD;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
@@ -101,4 +103,19 @@
         // during user creation pw last modified is set, thus it shouldn't expire
         a.authenticate(new SimpleCredentials(userId, userId.toCharArray()));
     }
+
+    @Test
+    public void testAuthenticateWithNewPasswordAttribute() throws Exception {
+        Authentication a = new UserAuthentication(getUserConfiguration(), root, userId);
+        SimpleCredentials sc = new SimpleCredentials(userId, userId.toCharArray());
+        sc.setAttribute(CREDENTIALS_ATTRIBUTE_NEWPASSWORD, "SureChangedMyPassword!");
+        try {
+            // the user should need to change the password on first login
+            // if new-pw attribute is present it will be used to reset password
+            assertTrue(a.authenticate(sc));
+        } finally {
+            // upon authentication the CREDENTIALS_ATTRIBUTE_NEWPASSWORD must be removed
+            assertNull(sc.getAttribute(CREDENTIALS_ATTRIBUTE_NEWPASSWORD));
+        }
+    }
 }
Index: oak-core/src/test/java/org/apache/jackrabbit/oak/security/user/PasswordExpiryHistoryTest.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- oak-core/src/test/java/org/apache/jackrabbit/oak/security/user/PasswordExpiryHistoryTest.java	(revision 1873068)
+++ oak-core/src/test/java/org/apache/jackrabbit/oak/security/user/PasswordExpiryHistoryTest.java	(date 1579795828000)
@@ -36,6 +36,7 @@
 import javax.security.auth.login.CredentialExpiredException;
 import java.util.List;
 
+import static org.apache.jackrabbit.oak.spi.security.user.UserConstants.CREDENTIALS_ATTRIBUTE_NEWPASSWORD;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.fail;
@@ -103,6 +104,8 @@
                         "credentials should contain pw change failure reason",
                         "New password is identical to the current password.",
                         attr);
+            } finally {
+                assertNull(pwChangeCreds.getAttribute(CREDENTIALS_ATTRIBUTE_NEWPASSWORD));
             }
         }
     }
@@ -134,6 +137,8 @@
                         "credentials should contain pw change failure reason",
                         "New password was found in password history.",
                         attr);
+            } finally {
+                assertNull(pwChangeCreds.getAttribute(CREDENTIALS_ATTRIBUTE_NEWPASSWORD));
             }
         }
     }
@@ -160,6 +165,8 @@
             } catch (CredentialExpiredException c) {
                 // success, pw found in history
                 assertNull(pwChangeCreds.getAttribute(PasswordHistoryException.class.getSimpleName()));
+            } finally {
+                assertNull(pwChangeCreds.getAttribute(CREDENTIALS_ATTRIBUTE_NEWPASSWORD));
             }
         }
     }
Index: oak-core/src/test/java/org/apache/jackrabbit/oak/security/user/ResetExpiredPasswordTest.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- oak-core/src/test/java/org/apache/jackrabbit/oak/security/user/ResetExpiredPasswordTest.java	(revision 1873068)
+++ oak-core/src/test/java/org/apache/jackrabbit/oak/security/user/ResetExpiredPasswordTest.java	(date 1579795828000)
@@ -30,10 +30,13 @@
 import org.apache.jackrabbit.oak.spi.security.user.UserConfiguration;
 import org.apache.jackrabbit.oak.spi.security.user.UserConstants;
 import org.apache.jackrabbit.oak.spi.security.user.util.PasswordUtil;
+import org.jetbrains.annotations.Nullable;
 import org.junit.Before;
 import org.junit.Test;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
@@ -43,6 +46,7 @@
 public class ResetExpiredPasswordTest extends AbstractSecurityTest implements UserConstants {
 
     private String userId;
+    private SimpleCredentials creds;
 
     @Before
     public void before() throws Exception {
@@ -63,13 +67,18 @@
     }
 
     private void authenticate(String expiredPw, Object newPw) throws LoginException {
-        SimpleCredentials creds = new SimpleCredentials(userId, expiredPw.toCharArray());
+        creds = new SimpleCredentials(userId, expiredPw.toCharArray());
         creds.setAttribute(UserConstants.CREDENTIALS_ATTRIBUTE_NEWPASSWORD, newPw);
 
         Authentication a = new UserAuthentication(getUserConfiguration(), root, userId);
         a.authenticate(creds);
     }
 
+    private static void assertCredentials(@Nullable SimpleCredentials sc) {
+        assertNotNull(sc);
+        assertNull(sc.getAttribute(CREDENTIALS_ATTRIBUTE_NEWPASSWORD));
+    }
+
     @Test
     public void testPasswordChangePersisted() throws Exception {
         authenticate(userId, "newPw");
@@ -78,11 +87,13 @@
         Root rootBasedOnSeparateSession = login(getAdminCredentials()).getLatestRoot();
         Tree userTree = rootBasedOnSeparateSession.getTree(getTestUser().getPath());
         assertTrue(PasswordUtil.isSame(userTree.getProperty(UserConstants.REP_PASSWORD).getValue(Type.STRING), "newPw"));
+        assertCredentials(creds);
     }
 
     @Test
     public void testAuthenticatePasswordExpiredThenChanged() throws Exception {
         authenticate(userId, userId);
+        assertCredentials(creds);
     }
 
     @Test
@@ -96,6 +107,7 @@
             Tree userTree = root.getTree(getTestUser().getPath());
             assertTrue(PasswordUtil.isSame(userTree.getProperty(UserConstants.REP_PASSWORD).getValue(Type.STRING), userId));
             assertEquals(0, userTree.getChild(UserConstants.REP_PWD).getProperty(UserConstants.REP_PASSWORD_LAST_MODIFIED).getValue(Type.LONG).longValue());
+            assertCredentials(creds);
         }
     }
 
@@ -110,6 +122,7 @@
             Tree userTree = root.getTree(getTestUser().getPath());
             assertTrue(PasswordUtil.isSame(userTree.getProperty(UserConstants.REP_PASSWORD).getValue(Type.STRING), userId));
             assertEquals(0, userTree.getChild(UserConstants.REP_PWD).getProperty(UserConstants.REP_PASSWORD_LAST_MODIFIED).getValue(Type.LONG).longValue());
+            assertCredentials(creds);
         }
     }
 }
