Index: oak-core/src/main/java/org/apache/jackrabbit/oak/security/authentication/user/UserAuthentication.java
===================================================================
--- oak-core/src/main/java/org/apache/jackrabbit/oak/security/authentication/user/UserAuthentication.java	(revision 1606101)
+++ oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/UserAuthentication.java	(revision )
@@ -14,23 +14,37 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.jackrabbit.oak.security.authentication.user;
+package org.apache.jackrabbit.oak.security.user;
 
 import java.util.Collections;
+import java.util.Date;
+import java.util.concurrent.TimeUnit;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
 import javax.jcr.Credentials;
 import javax.jcr.GuestCredentials;
 import javax.jcr.RepositoryException;
 import javax.jcr.SimpleCredentials;
+import javax.jcr.Value;
 import javax.security.auth.Subject;
+import javax.security.auth.login.CredentialExpiredException;
 import javax.security.auth.login.LoginException;
 
 import org.apache.jackrabbit.api.security.user.Authorizable;
 import org.apache.jackrabbit.api.security.user.User;
 import org.apache.jackrabbit.api.security.user.UserManager;
 import org.apache.jackrabbit.oak.api.AuthInfo;
-import org.apache.jackrabbit.oak.security.user.CredentialsImpl;
+import org.apache.jackrabbit.oak.api.PropertyState;
+import org.apache.jackrabbit.oak.api.Root;
+import org.apache.jackrabbit.oak.api.Type;
+import org.apache.jackrabbit.oak.namepath.NamePathMapper;
+import org.apache.jackrabbit.oak.spi.security.ConfigurationParameters;
 import org.apache.jackrabbit.oak.spi.security.authentication.Authentication;
 import org.apache.jackrabbit.oak.spi.security.authentication.ImpersonationCredentials;
+import org.apache.jackrabbit.oak.spi.security.authentication.PreAuthenticatedLogin;
+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.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -56,29 +70,32 @@
  * will return {@code false} indicating that this implementation is not able
  * to verify their validity.
  */
-class UserAuthentication implements Authentication {
+class UserAuthentication implements Authentication, UserConstants {
 
-    static final Credentials PRE_AUTHENTICATED = new Credentials() { };
-
     private static final Logger log = LoggerFactory.getLogger(UserAuthentication.class);
 
+    private static final String LAST_MOD_PATH = REP_PW + "/" + REP_PASSWORD_LAST_MODIFIED;
+
+    private final UserConfiguration config;
+    private final Root root;
     private final String userId;
-    private final UserManager userManager;
 
-    UserAuthentication(String userId, UserManager userManager) {
+    UserAuthentication(@Nonnull UserConfiguration config, @Nonnull Root root, @Nullable String userId) {
+        this.config = config;
+        this.root = root;
         this.userId = userId;
-        this.userManager = userManager;
     }
 
     //-----------------------------------------------------< Authentication >---
     @Override
     public boolean authenticate(Credentials credentials) throws LoginException {
-        if (userId == null || userManager == null || credentials == null) {
+        if (credentials == null || userId == null) {
             return false;
         }
 
         boolean success = false;
         try {
+            UserManager userManager = config.getUserManager(root, NamePathMapper.DEFAULT);
             Authorizable authorizable = userManager.getAuthorizable(userId);
             if (authorizable == null) {
                 return false;
@@ -94,6 +111,9 @@
             }
 
             if (credentials instanceof SimpleCredentials) {
+                if (isPasswordExpired(user)) {
+                    throw new CredentialExpiredException("User password has expired");
+                }
                 SimpleCredentials creds = (SimpleCredentials) credentials;
                 Credentials userCreds = user.getCredentials();
                 if (userId.equals(creds.getUserID()) && userCreds instanceof CredentialsImpl) {
@@ -107,7 +127,7 @@
                 checkSuccess(success, "Impersonation not allowed.");
             } else {
                 // guest login is allowed if an anonymous user exists in the content (see get user above)
-                success = (credentials instanceof GuestCredentials) || credentials == PRE_AUTHENTICATED;
+                success = (credentials instanceof GuestCredentials) || credentials == PreAuthenticatedLogin.PRE_AUTHENTICATED;
             }
         } catch (RepositoryException e) {
             throw new LoginException(e.getMessage());
@@ -141,5 +161,44 @@
             log.debug("Error while validating impersonation", e.getMessage());
         }
         return false;
+    }
+
+    @CheckForNull
+    private Long getPasswordLastModified(User user) throws RepositoryException {
+        if (user instanceof UserImpl) {
+            PropertyState property = ((UserImpl) user).getTree().getChild(REP_PW).getProperty(REP_PASSWORD_LAST_MODIFIED);
+            return (property != null) ? property.getValue(Type.LONG) : null;
+        } else {
+            Value[] lastMod = user.getProperty(LAST_MOD_PATH);
+            if (lastMod != null && lastMod.length > 0) {
+                return lastMod[0].getLong();
+            } else {
+                return null;
+            }
+        }
+    }
+
+    private boolean isPasswordExpired(@Nonnull User user) throws RepositoryException {
+        // the password of the "admin" user never expires
+        if (user.isAdmin()) {
+            return false;
+        }
+
+        boolean expired = false;
+        ConfigurationParameters params = config.getParameters();
+        int maxAge = params.getConfigValue(PARAM_PASSWORD_MAX_AGE, DEFAULT_PASSWORD_MAX_AGE);
+        if (maxAge > 0) {
+            // password expiry is enabled
+            Long passwordLastModified = getPasswordLastModified(user);
+            if (passwordLastModified == null) {
+                // no expiry property exists (yet) => expire!
+                expired = true;
+            } else {
+                // calculate expiry time (pw last mod + pw max age) and compare
+                long expiryTime = passwordLastModified + TimeUnit.MILLISECONDS.convert(maxAge, TimeUnit.DAYS);
+                expired = expiryTime < new Date().getTime();
+            }
+        }
+        return expired;
     }
 }
\ No newline at end of file
Index: oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/UserManagerImpl.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/UserManagerImpl.java	(revision 1606101)
+++ oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/UserManagerImpl.java	(revision )
@@ -19,6 +19,7 @@
 import java.io.UnsupportedEncodingException;
 import java.security.NoSuchAlgorithmException;
 import java.security.Principal;
+import java.util.Date;
 import java.util.Iterator;
 import javax.annotation.CheckForNull;
 import javax.annotation.Nonnull;
@@ -35,6 +36,7 @@
 import org.apache.jackrabbit.api.security.user.UserManager;
 import org.apache.jackrabbit.oak.api.Root;
 import org.apache.jackrabbit.oak.api.Tree;
+import org.apache.jackrabbit.oak.api.Type;
 import org.apache.jackrabbit.oak.namepath.NamePathMapper;
 import org.apache.jackrabbit.oak.plugins.nodetype.ReadOnlyNodeTypeManager;
 import org.apache.jackrabbit.oak.security.user.query.UserQueryManager;
@@ -51,6 +53,7 @@
 import org.apache.jackrabbit.oak.spi.security.user.action.DefaultAuthorizableActionProvider;
 import org.apache.jackrabbit.oak.spi.security.user.util.PasswordUtil;
 import org.apache.jackrabbit.oak.spi.security.user.util.UserUtil;
+import org.apache.jackrabbit.oak.util.NodeUtil;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -154,7 +157,7 @@
         Tree userTree = userProvider.createUser(userID, intermediatePath);
         setPrincipal(userTree, principal);
         if (password != null) {
-            setPassword(userTree, password, true);
+            setPassword(userTree, userID, password, true);
         }
 
         User user = new UserImpl(userID, userTree, this);
@@ -358,7 +361,7 @@
         authorizableTree.setProperty(UserConstants.REP_PRINCIPAL_NAME, principal.getName());
     }
 
-    void setPassword(Tree userTree, String password, boolean forceHash) throws RepositoryException {
+    void setPassword(Tree userTree, String userId, String password, boolean forceHash) throws RepositoryException {
         String pwHash;
         if (forceHash || PasswordUtil.isPlainTextPassword(password)) {
             try {
@@ -372,6 +375,27 @@
             pwHash = password;
         }
         userTree.setProperty(UserConstants.REP_PASSWORD, pwHash);
+
+        // set last-modified property if pw-expiry is enabled, the user is not
+        // admin and (if this is a new user) initial-pw-change is disabled.
+        if (!UserUtil.isAdmin(config, userId)
+                && supportsPasswordExpiry()
+                && !forceInitialPasswordChange(userTree)) {
+            Tree pwTree = new NodeUtil(userTree).getOrAddChild(UserConstants.REP_PW, UserConstants.NT_REP_PASSWORD).getTree();
+            pwTree.setProperty(UserConstants.REP_PASSWORD_LAST_MODIFIED, new Date().getTime(), Type.LONG);
+        }
+    }
+
+    private boolean supportsPasswordExpiry() {
+        return config.getConfigValue(UserConstants.PARAM_PASSWORD_MAX_AGE, UserConstants.DEFAULT_PASSWORD_MAX_AGE) > 0;
+    }
+
+    private boolean forceInitialPasswordChange(@Nonnull Tree userTree) {
+        if (userTree.getStatus() == Tree.Status.NEW) {
+            return config.getConfigValue(UserConstants.PARAM_PASSWORD_INITIAL_CHANGE, UserConstants.DEFAULT_PASSWORD_INITIAL_CHANGE);
+        } else {
+            return false;
+        }
     }
 
     private UserQueryManager getQueryManager() {
\ No newline at end of file
Index: oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/user/util/UserUtil.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/user/util/UserUtil.java	(revision 1606101)
+++ oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/user/util/UserUtil.java	(revision )
@@ -40,6 +40,11 @@
     }
 
     @Nonnull
+    public static boolean isAdmin(@Nonnull ConfigurationParameters parameters, @Nonnull String userId) {
+        return parameters.getConfigValue(PARAM_ADMIN_ID, DEFAULT_ADMIN_ID).equals(userId);
+    }
+
+    @Nonnull
     public static String getAdminId(@Nonnull ConfigurationParameters parameters) {
         return parameters.getConfigValue(PARAM_ADMIN_ID, DEFAULT_ADMIN_ID);
     }
\ No newline at end of file
Index: oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/UserConfigurationImpl.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/UserConfigurationImpl.java	(revision 1606101)
+++ oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/UserConfigurationImpl.java	(revision )
@@ -42,6 +42,7 @@
 import org.apache.jackrabbit.oak.spi.security.Context;
 import org.apache.jackrabbit.oak.spi.security.SecurityConfiguration;
 import org.apache.jackrabbit.oak.spi.security.SecurityProvider;
+import org.apache.jackrabbit.oak.spi.security.user.UserAuthenticationFactory;
 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;
@@ -90,10 +91,20 @@
         @Property(name = UserConstants.PARAM_SUPPORT_AUTOSAVE,
                 label = "Autosave Support",
                 description = "Configuration option to enable autosave behavior. Note: this config option is present for backwards compatibility with Jackrabbit 2.x and should only be used for broken code that doesn't properly verify the autosave behavior (see Jackrabbit API). If this option is turned on autosave will be enabled by default; otherwise autosave is not supported.",
-                boolValue = false)
+                boolValue = false),
+        @Property(name = UserConstants.PARAM_PASSWORD_MAX_AGE,
+                label = "Maximum Password Age",
+                description = "Maximum age in days a password may have. Values greater 0 will implicitly enable password expiry. A value of 0 indicates unlimited password age.",
+                intValue = UserConstants.DEFAULT_PASSWORD_MAX_AGE),
+        @Property(name = UserConstants.PARAM_PASSWORD_INITIAL_CHANGE,
+                label = "Change Password On First Login",
+                description = "When enabled, forces users to change their password upon first login. Note that a maximum password age must be set to a value > 0 in order to make this option functional.",
+                boolValue = UserConstants.DEFAULT_PASSWORD_INITIAL_CHANGE)
 })
 public class UserConfigurationImpl extends ConfigurationBase implements UserConfiguration, SecurityConfiguration {
 
+    private final UserAuthenticationFactory defaultAuthFactory = new UserAuthenticationFactoryImpl();
+
     public UserConfigurationImpl() {
         super();
     }
@@ -112,6 +123,19 @@
     @Override
     public String getName() {
         return NAME;
+    }
+
+    @Nonnull
+    @Override
+    public ConfigurationParameters getParameters() {
+        ConfigurationParameters params = super.getParameters();
+        if (!params.containsKey(UserConstants.PARAM_USER_AUTHENTICATION_FACTORY)) {
+            return ConfigurationParameters.of(
+                    params,
+                    ConfigurationParameters.of(UserConstants.PARAM_USER_AUTHENTICATION_FACTORY, defaultAuthFactory));
+        } else {
+            return params;
+        }
     }
 
     @Nonnull
Index: oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/UserImpl.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/UserImpl.java	(revision 1606101)
+++ oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/UserImpl.java	(revision )
@@ -44,7 +44,7 @@
     UserImpl(String id, Tree tree, UserManagerImpl userManager) throws RepositoryException {
         super(id, tree, userManager);
 
-        isAdmin = UserUtil.getAdminId(userManager.getConfig()).equals(id);
+        isAdmin = UserUtil.isAdmin(userManager.getConfig(), id);
     }
 
     //---------------------------------------------------< AuthorizableImpl >---
@@ -96,7 +96,7 @@
         }
         UserManagerImpl userManager = getUserManager();
         userManager.onPasswordChange(this, password);
-        userManager.setPassword(getTree(), password, true);
+        userManager.setPassword(getTree(), getID(),  password, true);
     }
 
     @Override
\ No newline at end of file
Index: oak-core/src/main/resources/org/apache/jackrabbit/oak/plugins/nodetype/write/builtin_nodetypes.cnd
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- oak-core/src/main/resources/org/apache/jackrabbit/oak/plugins/nodetype/write/builtin_nodetypes.cnd	(revision 1606101)
+++ oak-core/src/main/resources/org/apache/jackrabbit/oak/plugins/nodetype/write/builtin_nodetypes.cnd	(revision )
@@ -707,7 +707,12 @@
   mixin
   - rep:impersonators (STRING) protected multiple
 
+[rep:Password]
+  - * (UNDEFINED) protected
+  - * (UNDEFINED) protected multiple
+
 [rep:User] > rep:Authorizable, rep:Impersonatable
+  + rep:pw (rep:Password) = rep:Password protected
   - rep:password (STRING) protected
   - rep:disabled (STRING) protected
 
Index: oak-core/src/test/java/org/apache/jackrabbit/oak/security/user/UserAuthenticationFactoryImplTest.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/UserAuthenticationFactoryImplTest.java	(revision )
+++ oak-core/src/test/java/org/apache/jackrabbit/oak/security/user/UserAuthenticationFactoryImplTest.java	(revision )
@@ -0,0 +1,22 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.oak.security.user;
+
+public class UserAuthenticationFactoryImplTest {
+
+    // TODO add tests
+}
\ No newline at end of file
Index: oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/UserAuthenticationFactoryImpl.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/UserAuthenticationFactoryImpl.java	(revision )
+++ oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/UserAuthenticationFactoryImpl.java	(revision )
@@ -0,0 +1,38 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.oak.security.user;
+
+import org.apache.jackrabbit.oak.api.Root;
+import org.apache.jackrabbit.oak.spi.security.authentication.Authentication;
+import org.apache.jackrabbit.oak.spi.security.user.UserAuthenticationFactory;
+import org.apache.jackrabbit.oak.spi.security.user.UserConfiguration;
+
+import javax.annotation.CheckForNull;
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+public class UserAuthenticationFactoryImpl implements UserAuthenticationFactory {
+
+    public UserAuthenticationFactoryImpl() {
+    }
+
+    @Nonnull
+    @Override
+    public Authentication getAuthentication(UserConfiguration userConfiguration, @Nonnull Root root, @Nullable String userId) {
+        return new UserAuthentication(userConfiguration, root, userId);
+    }
+}
Index: oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/ConfigurationParameters.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/ConfigurationParameters.java	(revision 1606101)
+++ oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/ConfigurationParameters.java	(revision )
@@ -30,6 +30,7 @@
 import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
 
+import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
 import org.apache.jackrabbit.oak.commons.PropertiesUtil;
 import org.slf4j.Logger;
@@ -142,6 +143,19 @@
             options.put(String.valueOf(e.getKey()), e.getValue());
         }
         return new ConfigurationParameters(options);
+    }
+
+    /**
+     * Creates new a single valued configuration parameters instance from the
+     * given key and value.
+     *
+     * @param key The key
+     * @param value The value
+     * @return a new instance of configuration parameters.
+     */
+    @Nonnull
+    public static ConfigurationParameters of(@Nonnull String key, @Nonnull Object value) {
+        return new ConfigurationParameters(ImmutableMap.of(key, value));
     }
 
     /**
\ No newline at end of file
Index: oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/security/user/UserImportPwExpiryTest.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/security/user/UserImportPwExpiryTest.java	(revision )
+++ oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/security/user/UserImportPwExpiryTest.java	(revision )
@@ -0,0 +1,61 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.oak.jcr.security.user;
+
+import javax.annotation.CheckForNull;
+
+import org.apache.jackrabbit.oak.spi.security.ConfigurationParameters;
+import org.junit.Test;
+
+/**
+ * Testing user import with default {@link org.apache.jackrabbit.oak.spi.xml.ImportBehavior}
+ * and pw-expiry content
+ */
+public class UserImportPwExpiryTest extends AbstractImportTest {
+
+    @Override
+    protected String getTargetPath() {
+        return USERPATH;
+    }
+
+    @Override
+    protected String getImportBehavior() {
+        return null;
+    }
+
+    @CheckForNull
+    protected ConfigurationParameters getConfigurationParameters() {
+        // TODO: setup configuration with max_age
+        return ConfigurationParameters.EMPTY;
+    }
+
+    /**
+     * @since Oak 1.1
+     */
+    @Test
+    public void testImportUser() throws Exception {
+        // TODO import xml with rep:pwd node
+    }
+
+    /**
+     * @since Oak 1.1
+     */
+    @Test
+    public void testImportUserWithCustomPwdProperties() throws Exception {
+        // TODO
+    }
+}
Index: oak-core/src/main/java/org/apache/jackrabbit/oak/security/authentication/user/LoginModuleImpl.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- oak-core/src/main/java/org/apache/jackrabbit/oak/security/authentication/user/LoginModuleImpl.java	(revision 1606101)
+++ oak-core/src/main/java/org/apache/jackrabbit/oak/security/authentication/user/LoginModuleImpl.java	(revision )
@@ -23,6 +23,7 @@
 import java.util.Map;
 import java.util.Set;
 import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
 import javax.jcr.Credentials;
 import javax.jcr.GuestCredentials;
 import javax.jcr.SimpleCredentials;
@@ -32,6 +33,7 @@
 import javax.security.auth.login.LoginException;
 
 import org.apache.jackrabbit.oak.api.AuthInfo;
+import org.apache.jackrabbit.oak.api.Root;
 import org.apache.jackrabbit.oak.spi.security.ConfigurationParameters;
 import org.apache.jackrabbit.oak.spi.security.SecurityProvider;
 import org.apache.jackrabbit.oak.spi.security.authentication.AbstractLoginModule;
@@ -39,7 +41,9 @@
 import org.apache.jackrabbit.oak.spi.security.authentication.Authentication;
 import org.apache.jackrabbit.oak.spi.security.authentication.ImpersonationCredentials;
 import org.apache.jackrabbit.oak.spi.security.authentication.PreAuthenticatedLogin;
+import org.apache.jackrabbit.oak.spi.security.user.UserAuthenticationFactory;
 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.UserUtil;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -119,12 +123,12 @@
         PreAuthenticatedLogin preAuthLogin = getSharedPreAuthLogin();
         if (preAuthLogin != null) {
             userId = preAuthLogin.getUserId();
-            Authentication authentication = new UserAuthentication(userId, getUserManager());
-            success = authentication.authenticate(UserAuthentication.PRE_AUTHENTICATED);
+            Authentication authentication = getUserAuthentication(userId);
+            success = authentication != null && authentication.authenticate(PreAuthenticatedLogin.PRE_AUTHENTICATED);
         } else {
             userId = getUserId();
-            Authentication authentication = new UserAuthentication(userId, getUserManager());
-            success = authentication.authenticate(credentials);
+            Authentication authentication = getUserAuthentication(userId);
+            success = authentication != null && authentication.authenticate(credentials);
         }
 
         if (success) {
@@ -215,6 +219,22 @@
             ConfigurationParameters params = sp.getConfiguration(UserConfiguration.class).getParameters();
             return UserUtil.getAnonymousId(params);
         }
+    }
+
+    @CheckForNull
+    private Authentication getUserAuthentication(@Nullable String userId) {
+        SecurityProvider securityProvider = getSecurityProvider();
+        Root root = getRoot();
+        if (securityProvider != null && root != null) {
+            UserConfiguration uc = securityProvider.getConfiguration(UserConfiguration.class);
+            UserAuthenticationFactory factory = uc.getParameters().getConfigValue(UserConstants.PARAM_USER_AUTHENTICATION_FACTORY, null, UserAuthenticationFactory.class);
+            if (factory != null) {
+                return factory.getAuthentication(uc, root, userId);
+            } else {
+                log.error("No user authentication factory configured in user configuration.");
+            }
+        }
+        return null;
     }
 
     private AuthInfo createAuthInfo() {
Index: oak-core/src/test/java/org/apache/jackrabbit/oak/security/authentication/user/UserAuthenticationTest.java
===================================================================
--- oak-core/src/test/java/org/apache/jackrabbit/oak/security/authentication/user/UserAuthenticationTest.java	(revision 1606101)
+++ oak-core/src/test/java/org/apache/jackrabbit/oak/security/user/UserAuthenticationTest.java	(revision )
@@ -14,7 +14,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.jackrabbit.oak.security.authentication.user;
+package org.apache.jackrabbit.oak.security.user;
 
 import java.security.Principal;
 import java.util.ArrayList;
@@ -51,18 +51,12 @@
     public void before() throws Exception {
         super.before();
         userId = getTestUser().getID();
-        authentication = new UserAuthentication(userId, getUserManager(root));
+        authentication = new UserAuthentication(getUserConfiguration(), root, userId);
     }
 
     @Test
-    public void testAuthenticateWithoutUserManager() throws Exception {
-        UserAuthentication authentication = new UserAuthentication(userId, null);
-        assertFalse(authentication.authenticate(new SimpleCredentials(userId, userId.toCharArray())));
-    }
-
-    @Test
     public void testAuthenticateWithoutUserId() throws Exception {
-        UserAuthentication authentication = new UserAuthentication(null, getUserManager(root));
+        authentication = new UserAuthentication(getUserConfiguration(), root, null);
         assertFalse(authentication.authenticate(new SimpleCredentials(userId, userId.toCharArray())));
     }
 
@@ -80,7 +74,7 @@
     @Test
     public void testAuthenticateCannotResolveUser() throws Exception {
         SimpleCredentials sc = new SimpleCredentials("unknownUser", "pw".toCharArray());
-        Authentication a = new UserAuthentication(sc.getUserID(), getUserManager(root));
+        Authentication a = new UserAuthentication(getUserConfiguration(), root, sc.getUserID());
 
         assertFalse(a.authenticate(sc));
     }
@@ -89,7 +83,7 @@
     public void testAuthenticateResolvesToGroup() throws Exception {
         Group g = getUserManager(root).createGroup("g1");
         SimpleCredentials sc = new SimpleCredentials(g.getID(), "pw".toCharArray());
-        Authentication a = new UserAuthentication(sc.getUserID(), getUserManager(root));
+        Authentication a = new UserAuthentication(getUserConfiguration(), root, sc.getUserID());
 
         try {
             a.authenticate(sc);
\ No newline at end of file
Index: oak-core/src/test/java/org/apache/jackrabbit/oak/security/user/PasswordExpiryTest.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/PasswordExpiryTest.java	(revision )
+++ oak-core/src/test/java/org/apache/jackrabbit/oak/security/user/PasswordExpiryTest.java	(revision )
@@ -0,0 +1,95 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.oak.security.user;
+
+import javax.jcr.SimpleCredentials;
+import javax.security.auth.login.CredentialExpiredException;
+
+import com.google.common.collect.ImmutableMap;
+import org.apache.jackrabbit.oak.AbstractSecurityTest;
+import org.apache.jackrabbit.oak.spi.security.ConfigurationParameters;
+import org.apache.jackrabbit.oak.spi.security.authentication.Authentication;
+import org.apache.jackrabbit.oak.spi.security.user.UserConfiguration;
+import org.apache.jackrabbit.oak.spi.security.user.UserConstants;
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+public class PasswordExpiryTest extends AbstractSecurityTest {
+
+    private String userId;
+    private UserAuthentication authentication;
+
+    @Before
+    public void before() throws Exception {
+        super.before();
+        userId = getTestUser().getID();
+        authentication = new UserAuthentication(getUserConfiguration(), root, userId);
+    }
+
+    @Override
+    protected ConfigurationParameters getSecurityConfigParameters() {
+        ConfigurationParameters userConfig = ConfigurationParameters.of(UserConstants.PARAM_PASSWORD_MAX_AGE, 10);
+        return ConfigurationParameters.of(ImmutableMap.of(UserConfiguration.NAME, userConfig));
+    }
+
+    @Test
+    public void testCreateUser() {
+        // TODO test if creating a new user sets the required properties
+    }
+
+    @Test
+    public void testChangePassword() {
+        // TODO test if changing the pw resets the required properties
+    }
+
+    @Test
+    public void testAuthenticatePasswordExpiredNewUser() throws Exception {
+        Authentication a = new UserAuthentication(getUserConfiguration(), root, userId);
+        // during user creation pw last modified is set, thus it shouldn't expire
+        a.authenticate(new SimpleCredentials(userId, userId.toCharArray()));
+    }
+
+    @Test
+    public void testAuthenticatePasswordExpired() throws Exception {
+        Authentication a = new UserAuthentication(getUserConfiguration(), root, userId);
+        // set password last modified to beginning of epoch
+        root.getTree(getTestUser().getPath()).getChild(UserConstants.REP_PW).setProperty(UserConstants.REP_PASSWORD_LAST_MODIFIED, 0);
+        root.commit();
+        try {
+            a.authenticate(new SimpleCredentials(userId, userId.toCharArray()));
+            fail("Credentials should be expired");
+        } catch (CredentialExpiredException e) {
+            // success
+        }
+    }
+
+    @Test
+    public void testAuthenticatePasswordExpiredChangePassword() throws Exception {
+        Authentication a = new UserAuthentication(getUserConfiguration(), root, userId);
+        // set password last modified to beginning of epoch
+        root.getTree(getTestUser().getPath()).getChild(UserConstants.REP_PW).setProperty(UserConstants.REP_PASSWORD_LAST_MODIFIED, 0);
+        root.commit();
+
+        // changing the password should reset the pw last mod and the pw no longer be expired
+        getTestUser().changePassword(userId);
+        root.commit();
+        assertTrue(a.authenticate(new SimpleCredentials(userId, userId.toCharArray())));
+    }
+}
\ No newline at end of file
Index: oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/UserImporter.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/UserImporter.java	(revision 1606101)
+++ oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/UserImporter.java	(revision )
@@ -283,7 +283,7 @@
             }
 
             String pw = propInfo.getTextValue().getString();
-            userManager.setPassword(parent, pw, false);
+            userManager.setPassword(parent, a.getID(), pw, false);
 
             /*
             Execute authorizable actions for a NEW user at this point after
\ No newline at end of file
Index: oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/user/UserAuthenticationFactory.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/user/UserAuthenticationFactory.java	(revision )
+++ oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/user/UserAuthenticationFactory.java	(revision )
@@ -0,0 +1,46 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.oak.spi.security.user;
+
+import org.apache.jackrabbit.oak.api.Root;
+import org.apache.jackrabbit.oak.spi.security.authentication.Authentication;
+
+import javax.annotation.CheckForNull;
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+/**
+ * Provides a user management specific implementation of the
+ * {@link org.apache.jackrabbit.oak.spi.security.authentication.Authentication}
+ * interface to those {@link javax.security.auth.spi.LoginModule}s that verify
+ * a given authentication request by evaluation information exposed by the
+ * Jackrabbit user management API.
+ */
+public interface UserAuthenticationFactory {
+
+    /**
+     * Returns an implementation {@link org.apache.jackrabbit.oak.spi.security.authentication.Authentication}
+     * for the specified {@code userId}.
+     *
+     * @param userConfiguration The user configuration.
+     * @param root   The {@link org.apache.jackrabbit.oak.api.Root} that provides repository access.
+     * @param userId The userId for which a user authentication is provided.
+     * @return The authentication object specific to the provided user.
+     */
+    @Nonnull
+    Authentication getAuthentication(@Nonnull UserConfiguration userConfiguration, @Nonnull Root root, @Nullable String userId);
+}
Index: oak-core/src/test/java/org/apache/jackrabbit/oak/security/user/InitialPasswordChangeTest.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/InitialPasswordChangeTest.java	(revision )
+++ oak-core/src/test/java/org/apache/jackrabbit/oak/security/user/InitialPasswordChangeTest.java	(revision )
@@ -0,0 +1,79 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.oak.security.user;
+
+import javax.jcr.SimpleCredentials;
+import javax.security.auth.login.CredentialExpiredException;
+
+import com.google.common.collect.ImmutableMap;
+import org.apache.jackrabbit.api.security.user.User;
+import org.apache.jackrabbit.oak.AbstractSecurityTest;
+import org.apache.jackrabbit.oak.spi.security.ConfigurationParameters;
+import org.apache.jackrabbit.oak.spi.security.authentication.Authentication;
+import org.apache.jackrabbit.oak.spi.security.user.UserConfiguration;
+import org.apache.jackrabbit.oak.spi.security.user.UserConstants;
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.junit.Assert.fail;
+
+public class InitialPasswordChangeTest extends AbstractSecurityTest {
+
+    private String userId;
+    private UserAuthentication authentication;
+
+    @Before
+    public void before() throws Exception {
+        super.before();
+        userId = getTestUser().getID();
+        authentication = new UserAuthentication(getUserConfiguration(), root, userId);
+    }
+
+    @Override
+    protected ConfigurationParameters getSecurityConfigParameters() {
+        ConfigurationParameters userConfig = ConfigurationParameters.of(ImmutableMap.of(
+                UserConstants.PARAM_PASSWORD_INITIAL_CHANGE, Boolean.TRUE,
+                UserConstants.PARAM_PASSWORD_MAX_AGE, 10));
+        return ConfigurationParameters.of(ImmutableMap.of(UserConfiguration.NAME, userConfig));
+    }
+
+    @Test
+    public void testCreateUser() throws Exception {
+        // TODO: test that a new user has the properties set as expected
+    }
+
+    @Test
+    public void testAuthenticateForcePasswordChange() throws Exception {
+        try {
+            Authentication a = new UserAuthentication(getUserConfiguration(), root, userId);
+            a.authenticate(new SimpleCredentials(userId, userId.toCharArray()));
+            fail("Credentials should be expired");
+        } catch (CredentialExpiredException e) {
+            // success
+        }
+    }
+
+    @Test
+    public void testAuthenticateAfterPasswordChange() throws Exception {
+        User user = getTestUser();
+        user.changePassword(userId);
+        root.commit();
+
+        Authentication a = new UserAuthentication(getUserConfiguration(), root, userId);
+        a.authenticate(new SimpleCredentials(userId, userId.toCharArray()));
+    }
+}
\ No newline at end of file
Index: oak-core/src/test/java/org/apache/jackrabbit/oak/security/user/UserManagerImplTest.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/UserManagerImplTest.java	(revision 1606101)
+++ oak-core/src/test/java/org/apache/jackrabbit/oak/security/user/UserManagerImplTest.java	(revision )
@@ -100,14 +100,14 @@
 
         Tree userTree = root.getTree(user.getPath());
         for (String pw : pwds) {
-            userMgr.setPassword(userTree, pw, true);
+            userMgr.setPassword(userTree, testUserId, pw, true);
             String pwHash = userTree.getProperty(UserConstants.REP_PASSWORD).getValue(Type.STRING);
             assertNotNull(pwHash);
             assertTrue(PasswordUtil.isSame(pwHash, pw));
         }
 
         for (String pw : pwds) {
-            userMgr.setPassword(userTree, pw, false);
+            userMgr.setPassword(userTree, testUserId, pw, false);
             String pwHash = userTree.getProperty(UserConstants.REP_PASSWORD).getValue(Type.STRING);
             assertNotNull(pwHash);
             if (!pw.startsWith("{")) {
@@ -126,19 +126,21 @@
 
         Tree userTree = root.getTree(user.getPath());
         try {
-            userMgr.setPassword(userTree, null, true);
+            userMgr.setPassword(userTree, testUserId, null, true);
             fail("setting null password should fail");
         } catch (NullPointerException e) {
             // expected
         }
 
         try {
-            userMgr.setPassword(userTree, null, false);
+            userMgr.setPassword(userTree, testUserId, null, false);
             fail("setting null password should fail");
         } catch (NullPointerException e) {
             // expected
         }
     }
+
+    // TODO: add test-cases for setPassword with expiry and force-initial-pw-change
 
     @Test
     public void testGetPasswordHash() throws Exception {
\ No newline at end of file
Index: oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/user/UserConstants.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/user/UserConstants.java	(revision 1606101)
+++ oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/user/UserConstants.java	(revision )
@@ -30,6 +30,7 @@
     String NT_REP_AUTHORIZABLE = "rep:Authorizable";
     String NT_REP_AUTHORIZABLE_FOLDER = "rep:AuthorizableFolder";
     String NT_REP_USER = "rep:User";
+    String NT_REP_PASSWORD = "rep:Password";
     String NT_REP_GROUP = "rep:Group";
     @Deprecated
     String NT_REP_MEMBERS = "rep:Members";
@@ -41,10 +42,12 @@
     String REP_PRINCIPAL_NAME = "rep:principalName";
     String REP_AUTHORIZABLE_ID = "rep:authorizableId";
     String REP_PASSWORD = "rep:password";
+    String REP_PASSWORD_LAST_MODIFIED = "rep:passwordLastModified";
     String REP_DISABLED = "rep:disabled";
     String REP_MEMBERS = "rep:members";
     String REP_MEMBERS_LIST = "rep:membersList";
     String REP_IMPERSONATORS = "rep:impersonators";
+    String REP_PW = "rep:pw";
 
     Collection<String> GROUP_PROPERTY_NAMES = ImmutableSet.of(
             REP_PRINCIPAL_NAME,
@@ -90,6 +93,11 @@
     String DEFAULT_ANONYMOUS_ID = "anonymous";
 
     /**
+     * Mandatory configuration option denoting the user {@link org.apache.jackrabbit.oak.spi.security.authentication.Authentication} implementation to use in the login module.
+     */
+    String PARAM_USER_AUTHENTICATION_FACTORY = "userAuthenticationFactory";
+
+    /**
      * Configuration option to define the path underneath which user nodes
      * are being created.
      */
@@ -172,4 +180,26 @@
      * be reviewed and adjusted accordingly.</p>
      */
     String PARAM_SUPPORT_AUTOSAVE = "supportAutoSave";
+
+    /**
+     * Optional configuration parameter indicating the maximum age in days a password may have
+     * before it expires. If the value specified is > 0, password expiry is implicitly enabled.
+     */
+    String PARAM_PASSWORD_MAX_AGE = "passwordMaxAge";
+
+    /**
+     * Default value for {@link #PARAM_PASSWORD_MAX_AGE}
+     */
+    int DEFAULT_PASSWORD_MAX_AGE = 0;
+
+    /**
+     * Optional configuration parameter indicating whether users must change their passwords
+     * on first login. If enabled, passwords are immediately expired upon user creation.
+     */
+    String PARAM_PASSWORD_INITIAL_CHANGE = "initialPasswordChange";
+
+    /**
+     * Default value for {@link #PARAM_PASSWORD_INITIAL_CHANGE}
+     */
+    boolean DEFAULT_PASSWORD_INITIAL_CHANGE = false;
 }
Index: oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/PreAuthenticatedLogin.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/PreAuthenticatedLogin.java	(revision 1606101)
+++ oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/PreAuthenticatedLogin.java	(revision )
@@ -16,12 +16,16 @@
  */
 package org.apache.jackrabbit.oak.spi.security.authentication;
 
+import javax.jcr.Credentials;
+
 /**
  * {@code PreAuthenticatedLogin} is used as marker in the shared map of the login context. it indicates that the
  * respective user is pre authenticated on an external system. Note that is class is only used internally by the
  * login modules and cannot be "abused" from outside.
  */
 final public class PreAuthenticatedLogin {
+
+    public static final Credentials PRE_AUTHENTICATED = new Credentials() { };
 
     private final String userId;
 
\ No newline at end of file
