diff --git a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/dss/reporting-plugins/password-reset-api/lib/persistentkeyvaluestore-source/PersistentKeyValueStore.java b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/dss/reporting-plugins/password-reset-api/lib/persistentkeyvaluestore-source/PersistentKeyValueStore.java
index c1aec05d6bbd423904e249ee5c068aa4ce0371ba..cbbd3a4b2f7af536cd202d4f099ab8a967cc84e7 100644
--- a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/dss/reporting-plugins/password-reset-api/lib/persistentkeyvaluestore-source/PersistentKeyValueStore.java
+++ b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/dss/reporting-plugins/password-reset-api/lib/persistentkeyvaluestore-source/PersistentKeyValueStore.java
@@ -1,27 +1,88 @@
 package ch.ethz.sis;
 
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
 import java.io.Serializable;
+import java.util.Properties;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentMap;
 
+import ch.systemsx.cisd.openbis.dss.generic.server.DataStoreServer;
 
 
 public class PersistentKeyValueStore {
+
+	private static final String KEY_STORE_FILE;
 	private static ConcurrentMap<String, Serializable> keyStore = new ConcurrentHashMap<>();
 	
-	public static void put(String key, Serializable value) {
+	static {
+		Properties properties = DataStoreServer.getConfigParameters().getProperties();
+		String storerootDir = properties.getProperty("storeroot-dir");
+		KEY_STORE_FILE = storerootDir + "/" +  "PersistentKeyValueStore.bin";
+		load();
+	}
+
+	//
+	// Public API
+	//
+	public synchronized static void put(String key, Serializable value) {
 		keyStore.put(key, value);
+		save();
+		print();
 	}
-	
-	public static Serializable get(String key) {
+
+	public synchronized static Serializable get(String key) {
+		print();
 		return keyStore.get(key);
 	}
-	
-	public static void remove(String key) {
+
+	public synchronized static void remove(String key) {
 		keyStore.remove(key);
+		save();
 	}
-	
-	public static boolean containsKey(String key) {
+
+	public synchronized static boolean containsKey(String key) {
+		print();
 		return keyStore.containsKey(key);
 	}
+
+	private synchronized static void print() {
+		for (String key : keyStore.keySet()) {
+			System.out.println(key + " - " + keyStore.get(key));
+		}
+	}
+	
+	//
+	// save / load
+	//
+	private static void save() {
+		try (FileOutputStream fos = new FileOutputStream(KEY_STORE_FILE))
+		{
+			ObjectOutputStream oos = new ObjectOutputStream(fos);
+			oos.writeObject(keyStore);
+			oos.close();
+		} catch (IOException e)
+		{
+			e.printStackTrace();
+		}
+	}
+
+	@SuppressWarnings("unchecked")
+	private static void load() {
+		try (FileInputStream fis = new FileInputStream(KEY_STORE_FILE)) {
+			if (new File(KEY_STORE_FILE).exists()) {
+		        ObjectInputStream ois = new ObjectInputStream(fis);
+		        keyStore = (ConcurrentMap<String, Serializable>) ois.readObject();
+		        ois.close();
+			}
+		} catch (IOException | ClassNotFoundException e)
+		{
+			e.printStackTrace();
+		}
+	}
+
 }
diff --git a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/dss/reporting-plugins/password-reset-api/lib/persistentkeyvaluestore.jar b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/dss/reporting-plugins/password-reset-api/lib/persistentkeyvaluestore.jar
index d3f77f6a23766b0947ddd07d80311cdfe1069101..1f2a896f4205e7371109844e1328390ff6557c8f 100644
Binary files a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/dss/reporting-plugins/password-reset-api/lib/persistentkeyvaluestore.jar and b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/dss/reporting-plugins/password-reset-api/lib/persistentkeyvaluestore.jar differ
diff --git a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/dss/reporting-plugins/password-reset-api/password-reset-api.py b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/dss/reporting-plugins/password-reset-api/password-reset-api.py
index 486988df762a7fdf8297c09894d403c9566a9651..25067b6783602f4849a6576293c651b2514f247c 100644
--- a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/dss/reporting-plugins/password-reset-api/password-reset-api.py
+++ b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/dss/reporting-plugins/password-reset-api/password-reset-api.py
@@ -54,7 +54,7 @@ def sendResetPasswordEmail(tr, userId, baseUrl):
     sendResetPasswordEmailInternal(tr, emailAddress, userId, token, baseUrl)
 
 def resetPassword(tr, userId, token):
-    if tokenIsValid(userId, token):
+    if tokenIsValid(tr, userId, token):
         email = getUserEmail(tr, userId)
         resetPasswordInternal(tr, email, userId)
         PersistentKeyValueStore.remove(userId + RESET_TOKEN_KEY_POSTFIX)
@@ -78,8 +78,17 @@ def getJsonForData(data):
     jsonValue = objectMapper.writeValueAsString(data);
     return jsonValue;
 
-def tokenIsValid(userId, token):
-    return PersistentKeyValueStore.get(userId + RESET_TOKEN_KEY_POSTFIX) == token
+def tokenIsValid(tr, userId, token):
+    tokenAndTimestamp = PersistentKeyValueStore.get(userId + RESET_TOKEN_KEY_POSTFIX)
+    if (tokenAndTimestamp != None and tokenAndTimestamp["token"] == token):
+        timestampNow = time.time()
+        deltaInSeconds = timestampNow - float(tokenAndTimestamp["timestamp"])
+        maxDelayInMinutes = float(getProperty(tr, "max-delay-in-minutes"))
+        if deltaInSeconds < maxDelayInMinutes * 60:
+            return True
+        else:
+            PersistentKeyValueStore.remove(userId + RESET_TOKEN_KEY_POSTFIX)
+    return False
 
 def sendResetPasswordEmailInternal(tr, email, userId, token, baseUrl):
     passwordResetLink = getPasswordResetLink(email, userId, token, baseUrl)
diff --git a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/dss/reporting-plugins/password-reset-api/plugin.properties b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/dss/reporting-plugins/password-reset-api/plugin.properties
index 18c916fc9a59e026ae19951400e3bf2897d5a9ec..e8a94ee542c2d7e3b9267c614246847b828e0b10 100644
--- a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/dss/reporting-plugins/password-reset-api/plugin.properties
+++ b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/dss/reporting-plugins/password-reset-api/plugin.properties
@@ -1,6 +1,7 @@
 label = Password Reset API
 class = ch.systemsx.cisd.openbis.dss.generic.server.plugins.jython.JythonIngestionService
 script-path = password-reset-api.py
+max-delay-in-minutes = 2880
 password-reset-request-subject = ELN-LIMS password reset for %s
 password-reset-request-body = Hi,\n\nA request has been made to reset the password of user %s.\n\nClick on this link in order to get a new password:\n%s\n\nSincere regards,\nYour ELN-LIMS Team
 new-password-subject = ELN-LIMS new password for %s