---
title: Store multiple passwords for managed users
description: This sample demonstrates how to set up multiple passwords for managed users and how to synchronize separate passwords to different external resources.
component: pingidm
version: 8.1
page_id: pingidm:samples-guide:multiple-passwords
canonical_url: https://docs.pingidentity.com/pingidm/8.1/samples-guide/multiple-passwords.html
keywords: ["Samples", "Multiple Passwords", "Storage"]
section_ids:
  multiple-passwords-config: Configure the multiple passwords sample
  multiple-passwords-history-policy: Password history policy
  ldap-config-multiple-passwords: LDAP server configuration
  run-sample-multiple-passwords: Show multiple accounts
  run-sample-multiple-passwords-history: Show the password history policy
---

# Store multiple passwords for managed users

This sample demonstrates how to set up multiple passwords for managed users and how to synchronize separate passwords to different external resources.

|   |                                                                                                                                                                                                                                                                                                                   |
| - | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|   | You cannot run this sample through the admin UI. To make the sample work with the admin UI, set the `viewable` and `required` fields of the `password` property in the `conf/managed.json` file as follows:```json
"password" : {
    "title" : "Password",
    "type" : "string",
    "viewable" : true,
...
``` |

## Configure the multiple passwords sample

|   |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |
| - | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|   | Starting with IDM 8.1, the [legacy admin UI is deprecated](../release-notes/deprecated-functionality.html#legacy-admin-ui-deprecated) and is no longer bundled with IDM. New deployments should use the [Platform admin UI](../setup-guide/platform-admin-ui.html), which is the replacement for the legacy admin UI.Both UIs are available as separate downloads from the [Backstage download site](https://backstage.forgerock.com/downloads):- To install the Platform admin UI, follow the steps in [Install the Platform admin UI for standalone IDM](../setup-guide/platform-admin-ui.html).

- To continue using the legacy admin UI, follow the steps in [Install the legacy admin UI](../setup-guide/legacy-admin-ui.html). |

This sample assumes the following scenario:

* The managed/user repository is the source system.

* There are two target LDAP servers—`ldap` and `ldap2`.

  For the purposes of this sample, the two servers are represented by two separate organizational units on a single PingDS (DS) instance.

* Managed user objects have two additional password fields, each mapped to one of the two LDAP servers.

* Both LDAP servers have a requirement for a password history policy, but the history size differs for the two policies.

  The sample shows how to extend the password history policy described in [Password history policy](../security-guide/passwords.html#password-history) to apply to multiple password fields.

* The value of a managed user's `password` field is used by default for the additional passwords *unless* the CREATE, UPDATE, or PATCH requests on the managed user explicitly contain a value for these additional passwords.

The sample includes several customized configuration files in the `samples/multiple-passwords/conf/` directory. These customizations are crucial to the sample functionality and are described in detail in the following list:

* `provisioner.openicf-ldap.json`

  Configures the connection to the first LDAP directory.

* `provisioner.openicf-ldap2.json`

  Configures the connection to the second LDAP directory.

* `sync.json`

  Provides the mappings from the IDM managed user repository to the respective LDAP servers. The file includes two mappings:

  * A mapping from IDM managed users to the LDAP user objects at the `system/ldap/account` endpoint. This endpoint represents the `ou=People` subtree.

  * A mapping from IDM managed users to the LDAP user objects at the `system/ldap2/account` endpoint. This endpoint represents the `ou=Customers` subtree.

  Both mappings include an explicit mapping from `ldapPassword` and `ldap2Password` to `userPassword` in the standard property mappings. Because these passwords are encrypted, a transform script is defined which uses `openidm.decrypt()` to set the value on the target object.

* `managed.json`

  Contains a customized schema for managed users that includes the additional password fields.

  This file has been customized as follows:

  * The schema includes an `ldapPassword` field that is mapped to the accounts in the `system/ldap/accounts` target. This field is subject to the standard policies associated with the `password` field of a managed user. In addition, the `ldapPassword` must contain two capital letters instead of the usual one capital letter requirement.

  * The schema includes an `ldap2Password` field that is mapped to the accounts in the `system/ldap2/accounts` target. This field is subject to the standard policies associated with the `password` field of a managed user. In addition, the `ldap2Password` must contain two numbers instead of the usual one number requirement.

  * A custom password history policy (`"policyId" : "is-new"`) applies to each of the two mapped password fields `ldapPassword`, and `ldap2Password`.

* `router.json`

  A scripted filter on `managed/user` and `policy/managed/user` that populates the values of the additional password fields with the value of the main `password` field if the additional fields are not included in the request content.

The sample includes the following customized scripts in the `script` directory:

* `onCreate-user-custom.js` and `onUpdate-user-custom.js` are used for validation of the password history policy when a user is created or updated.

* `pwpolicy.js` is an additional policy script for the password history policy.

* `set-additional-passwords.js` populates the values of the additional password fields with the value of the main `password` field if the additional fields are not included in the request content.

## Password history policy

The sample includes a custom password history policy. Although the sample demonstrates the history of password attributes only, you can use this policy to enforce history validation on any managed object property.

The following configuration changes set up the password history policy:

* A `fieldHistory` property is added to managed users. The value of this field is a map of field names to a list of historical values for that field. These lists of values are used by the policy to determine if a new value has previously been used.

  The `fieldHistory` property is not accessible over REST by default, and cannot be modified.

* The `onCreate-user-custom.js` script performs the standard `onCreate` tasks for a managed user object but also stores the initial value of each of the fields for which IDM should keep a history. The script is passed the following configurable properties:

  |                 |                                           |
  | --------------- | ----------------------------------------- |
  | `historyFields` | a list of the fields to store history on. |
  | `historySize`   | the number of historical fields to store. |

* The `onUpdate-user-custom.js` script compares the old and new values of the historical fields on update events to determine if the values have changed. When a new value is detected, it is stored in the list of historical values for that field.

  This script also contains logic to deal with the comparison of encrypted field values. The script is passed the following configurable properties:

  |                 |                                           |
  | --------------- | ----------------------------------------- |
  | `historyFields` | a list of the fields to store history on. |
  | `historySize`   | the number of historical fields to store. |

* The `pwpolicy.js` script contains the additional policy definition for the password history policy. This script compares the new field value with the list of historical values for each field.

  The policy configuration (`policy.json`) references this script in its `additionalFiles` list, so that the policy service loads the policy definition. The new policy takes a `historyLength` parameter, which indicates the number of historical values to enforce the policy on. This number must not exceed the `historySize` specified in the `onCreate` and `onUpdate` scripts.

* The `ldapPassword` and `ldap2Password` fields in the managed user schema have been updated with the policy. For the purposes of this sample the `historySize` has been set to 2 for `ldapPassword` and to 4 for `ldap2Password`.

## LDAP server configuration

1. [Set up DS](start-here.html#ldap-server-config) using `/path/to/openidm/samples/multiple-passwords/data/Example.ldif` .

2. Perform an `ldapsearch` on the LDAP directory, and take note of the organizational units:

   ```
   /path/to/opendj/bin/ldapsearch \
   --port 1636 \
   --useSSL \
   --usePkcs12TrustStore /path/to/opendj/config/keystore \
   --trustStorePassword:file /path/to/opendj/config/keystore.pin \
   --hostname localhost \
   --baseDN "dc=example,dc=com" \
   --bindDN uid=admin \
   --bindPassword password \
   "ou=*" \
   ou
   dn: ou=People,dc=example,dc=com
   ou: People

   dn: ou=Customers,dc=example,dc=com
   ou: people
   ou: Customers
   ```

   The organizational units, `ou=People` and `ou=Customers`, represent the two different target LDAP systems that our mappings point to.

## Show multiple accounts

This section starts IDM with the sample configuration, then creates a user with multiple passwords, adhering to the different policies in the configured password policy. The section tests that the user was synchronized to two separate LDAP directories, with the different required passwords, and that the user can bind to each of these LDAP directories.

1. Prepare IDM as described in [Prepare IDM](start-here.html#preparing-openidm), then start the server with the configuration for the multiple passwords sample:

   ```
   cd /path/to/openidm/
   ./startup.sh -p samples/multiple-passwords
   ```

2. Create a user, jdoe, providing individual values for each of the different password fields, that comply with the three different password policies:

   ```
   curl \
   --header "X-OpenIDM-Username: openidm-admin" \
   --header "X-OpenIDM-Password: openidm-admin" \
   --header "Accept-API-Version: resource=1.0" \
   --header "Content-Type: application/json" \
   --request POST \
   --data '{
     "userName": "jdoe",
     "givenName": "John",
     "sn": "Doe",
     "displayName": "John Doe",
     "mail": "john.doe@example.com",
     "password": "Secretpw1",
     "ldapPassword": "S3cretPw",
     "ldap2Password": "Secr3tpw1"
   }' \
   "http://localhost:8080/openidm/managed/user?_action=create"
   {
     "_id": "5ce188f6-252b-429e-aad1-4d8754d77de5",
     "_rev": "00000000d2d76089",
     "userName": "jdoe",
     "givenName": "John",
     "sn": "Doe",
     "displayName": "John Doe",
     "mail": "john.doe@example.com",
     "ldapPassword": {
       "$crypto": {
         "type": "x-simple-encryption",
         "value": {
           "cipher": "AES/CBC/PKCS5Padding",
           "stableId": "openidm-sym-default",
           "salt": "lkackh...",
           "data": "T0mljk...",
           "keySize": 16,
           "purpose": "idm.password.encryption",
           "iv": "ehSMbdNn...",
           "mac": "PssPOsW..."
         }
       }
     },
     "ldap2Password": {
       "$crypto": {
         "type": "x-simple-encryption",
         "value": {
           "cipher": "AES/CBC/PKCS5Padding",
           "stableId": "openidm-sym-default",
           "salt": "lSzMTU54...",
           "data": "UWlQo5Ws...",
           "keySize": 16,
           "purpose": "idm.password.encryption",
           "iv": "ehSMbdN...",
           "mac": "PssPOs..."
         }
       }
     },
     "accountStatus": "active",
     "effectiveRoles": [],
     "effectiveAssignments": [],
     "roles": []
   }
   ```

   The user has been created with three different passwords that comply with three distinct password policies. The passwords have been encrypted as defined in the `managed.json` file.

   |   |                                                                                                                                                                         |
   | - | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
   |   | In this example, the user has been created with ID `5ce188f6-252b-429e-aad1-4d8754d77de5`. You will need the user ID when you update the entry later in this procedure. |

3. As a result of implicit synchronization, two separate LDAP accounts should have been created for user jdoe on our two simulated LDAP servers. For more information about implicit synchronization, refer to [Synchronization types](../synchronization-guide/sync-types.html).

4. Query the IDs in the LDAP directory as follows:

   ```
   curl \
   --header "X-OpenIDM-Username: openidm-admin" \
   --header "X-OpenIDM-Password: openidm-admin" \
   --header "Accept-API-Version: resource=1.0" \
   --request GET \
   "http://localhost:8080/openidm/system/ldap/account?_queryId=query-all-ids"
   {
     "result": [
       {
         "_id": "00452010-a164-4065-9f84-3e4636a3ee20",
       },
       {
         "_id": "e5b35587-2d7c-4faa-b3e5-962f5a4ada5c",
       }
     ],
     ...
   }
   ```

   jdoe has two entries—one in `ou=People` and one in `ou=Customers`.

5. To verify the passwords propagated correctly, perform an LDAP search, bound using each of the jdoe accounts, against the rootDSE.

   |   |                                                                                                                                                                                                                                                               |
   | - | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
   |   | For the following commands, make sure to enter 2 or 3 at the following prompt:```
   Do you trust this server certificate?

     1) No
     2) Yes, for this session only
     3) Yes, also add it to a truststore
     4) View certificate details

   Enter choice [1]: 2
   ``` |

   ```
   /path/to/opendj/bin/ldapsearch \
   --hostname localhost \
   --port 1636 \
   --useSSL \
   --bindDN uid=jdoe,ou=People,dc=example,dc=com \
   --bindPassword S3cretPw \
   --searchScope base \
   --baseDN "" "(objectClass=*)"
   dn:
   objectClass: top
   objectClass: ds-root-dse
   ```

   ```
   /path/to/opendj/bin/ldapsearch \
   --hostname localhost \
   --port 1636 \
   --useSSL \
   --bindDN uid=jdoe,ou=Customers,dc=example,dc=com \
   --bindPassword Secr3tpw1 \
   --searchScope base \
   --baseDN "" "(objectClass=*)"
   dn:
   objectClass: top
   objectClass: ds-root-dse
   ```

6. Patch jdoe's managed user entry (`5ce188f6-252b-429e-aad1-4d8754d77de5`) to change his `ldapPassword`:

   ```
   curl \
   --header "X-OpenIDM-Username: openidm-admin" \
   --header "X-OpenIDM-Password: openidm-admin" \
   --header "Accept-API-Version: resource=1.0" \
   --header "Content-Type: application/json" \
   --request PATCH \
   --data '[ {
     "operation": "replace",
     "field": "ldapPassword",
     "value": "TTestw0rd"
   } ]' \
   "http://localhost:8080/openidm/managed/user/5ce188f6-252b-429e-aad1-4d8754d77de5"
   {
     "_id": "5ce188f6-252b-429e-aad1-4d8754d77de5",
     "_rev": "000000001298f6a6",
     "userName": "jdoe",
     "givenName": "John",
     "sn": "Doe",
     "displayName": "John Doe",
     ...
     "ldapPassword": {
       "$crypto": {
         "type": "x-simple-encryption",
         "value": {
           "cipher": "AES/CBC/PKCS5Padding",
           "stableId": "openidm-sym-default",
           "salt": "Vlco8e...",
           "data": "INj9lk...",
           "keySize": 16,
           "purpose": "idm.password.encryption",
           "iv": "ehSMbdNn...",
           "mac": "PssPOsW..."
         }
       }
     },
     ...
   }
   ```

7. To verify the password change propagated correctly, perform an LDAP search, bound using jdoe from the People organizational unit, against the rootDSE.

   |   |                                                                                                                                                                                                                                                                  |
   | - | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
   |   | For the following command, make sure to enter `2` or `3` at the following prompt:```
   Do you trust this server certificate?

     1) No
     2) Yes, for this session only
     3) Yes, also add it to a truststore
     4) View certificate details

   Enter choice [1]: 2
   ``` |

   ```
   /path/to/opendj/bin/ldapsearch \
   --hostname localhost \
   --port 1636 \
   --useSSL \
   --bindDN uid=jdoe,ou=People,dc=example,dc=com \
   --bindPassword TTestw0rd \
   --searchScope base \
   --baseDN "" "(objectClass=*)"
   dn:
   objectClass: top
   objectClass: ds-root-dse
   ```

## Show the password history policy

This section demonstrates the password history policy by patching jdoe's managed user entry, changing his `ldapPassword` multiple times.

1. Send the following patch requests, changing the value of jdoe's `ldapPassword` each time:

   ```
   curl \
   --header "X-OpenIDM-Username: openidm-admin" \
   --header "X-OpenIDM-Password: openidm-admin" \
   --header "Accept-API-Version: resource=1.0" \
   --header "Content-Type: application/json" \
   --request PATCH \
   --data '[ {
     "operation": "replace",
     "field": "ldapPassword",
     "value": "TTestw0rd1"
   } ]' \
   "http://localhost:8080/openidm/managed/user/5ce188f6-252b-429e-aad1-4d8754d77de5"
   {
     "_id": "5ce188f6-252b-429e-aad1-4d8754d77de5",
     "_rev": "00000000a92657c7",
     "userName": "jdoe",
     "givenName": "John",
     "sn": "Doe",
     "displayName": "John Doe",
     "mail": "john.doe@example.com",
     ...
     "ldapPassword": {
       "$crypto": {
         "type": "x-simple-encryption",
         "value": {
           "cipher": "AES/CBC/PKCS5Padding",
           "stableId": "openidm-sym-default",
           "salt": "TjolL7...",
           "data": "Unbalo...",
           "keySize": 16,
           "purpose": "idm.password.encryption",
           "iv": "ehSMbdNn...",
           "mac": "PssPOsW..."
         }
       }
     },
     ...
   }
   ```

   ```
   curl \
   --header "X-OpenIDM-Username: openidm-admin" \
   --header "X-OpenIDM-Password: openidm-admin" \
   --header "Accept-API-Version: resource=1.0" \
   --header "Content-Type: application/json" \
   --request PATCH \
   --data '[ {
     "operation": "replace",
     "field": "ldapPassword",
     "value": "TTestw0rd2"
   } ]' \
   "http://localhost:8080/openidm/managed/user/5ce188f6-252b-429e-aad1-4d8754d77de5"
   {
     "_id": "5ce188f6-252b-429e-aad1-4d8754d77de5",
     "_rev": "00000000dc6160c8",
     "userName": "jdoe",
     "givenName": "John",
     "sn": "Doe",
     "displayName": "John Doe",
     ...
     "ldapPassword": {
       "$crypto": {
         "type": "x-simple-encryption",
         "value": {
           "cipher": "AES/CBC/PKCS5Padding",
           "stableId": "openidm-sym-default",
           "salt": "Ynio9n...",
           "data": "R0ol2b...",
           "keySize": 16,
           "purpose": "idm.password.encryption",
           "iv": "ehSMbdNn...",
           "mac": "PssPOsW..."
         }
       }
     },
     ...
   }
   ```

   ```
   curl \
   --header "X-OpenIDM-Username: openidm-admin" \
   --header "X-OpenIDM-Password: openidm-admin" \
   --header "Accept-API-Version: resource=1.0" \
   --header "Content-Type: application/json" \
   --request PATCH \
   --data '[ {
     "operation": "replace",
     "field": "ldapPassword",
     "value": "TTestw0rd3"
   } ]' \
   "http://localhost:8080/openidm/managed/user/5ce188f6-252b-429e-aad1-4d8754d77de5"
   {
     "_id": "5ce188f6-252b-429e-aad1-4d8754d77de5",
     "_rev": "00000000a92657c7",
     "userName": "jdoe",
     "givenName": "John",
     "sn": "Doe",
     "displayName": "John Doe",
     ...
     "ldapPassword": {
       "$crypto": {
         "type": "x-simple-encryption",
         "value": {
           "cipher": "AES/CBC/PKCS5Padding",
           "stableId": "openidm-sym-default",
           "salt": "9kilajT...",
           "data": "Hnkja98...",
           "keySize": 16,
           "purpose": "idm.password.encryption",
           "iv": "ehSMbdNn...",
           "mac": "PssPOsW..."
         }
       }
     },
     ...
   }
   ```

   User jdoe now has a *history* of `ldapPassword` values, that contains `TTestw0rd3`, `TTestw0rd2`, `TTestw0rd1`, and `TTestw0rd`, in that order.

2. The history size for the `ldapPassword` policy is set to 2. To demonstrate the history policy, attempt to patch jdoe's entry with a password value that was used in his previous 2 password changes: `TTestw0rd2`:

   ```
   curl \
   --header "X-OpenIDM-Username: openidm-admin" \
   --header "X-OpenIDM-Password: openidm-admin" \
   --header "Accept-API-Version: resource=1.0" \
   --header "Content-Type: application/json" \
   --request PATCH \
   --data '[ {
     "operation": "replace",
     "field": "ldapPassword",
     "value": "TTestw0rd2"
   } ]' \
   "http://localhost:8080/openidm/managed/user/5ce188f6-252b-429e-aad1-4d8754d77de5"
   {
     "code": 403,
     "reason": "Forbidden",
     "message": "Failed policy validation",
     "detail": {
       "result": false,
       "failedPolicyRequirements": [
         {
           "policyRequirements": [
             {
               "policyRequirement": "IS_NEW"
             }
           ],
           "property": "ldapPassword"
         }
       ]
     }
   }
   ```

   The password change fails the `IS_NEW` policy requirement.

3. Change jdoe's `ldapPassword` to a value not used in the previous two updates:

   ```
   curl \
   --header "X-OpenIDM-Username: openidm-admin" \
   --header "X-OpenIDM-Password: openidm-admin" \
   --header "Accept-API-Version: resource=1.0" \
   --header "Content-Type: application/json" \
   --request PATCH \
   --data '[ {
     "operation": "replace",
     "field": "ldapPassword",
     "value": "TTestw0rd"
   } ]' \
   "http://localhost:8080/openidm/managed/user/5ce188f6-252b-429e-aad1-4d8754d77de5"
   {
     "_id": "5ce188f6-252b-429e-aad1-4d8754d77de5",
     "_rev": "00000000792afa08",
     "userName": "jdoe",
     "givenName": "John",
     "sn": "Doe",
     "displayName": "John Doe",
     ...
     "ldapPassword": {
       "$crypto": {
         "type": "x-simple-encryption",
         "value": {
           "cipher": "AES/CBC/PKCS5Padding",
           "stableId": "openidm-sym-default",
           "salt": "Ivmal5...",
           "data": "0mkywe...",
           "keySize": 16,
           "purpose": "idm.password.encryption",
           "iv": "ehSMbdNn...",
           "mac": "PssPOsW..."
         }
       }
     },
     ...
   }
   ```

   The password change succeeds.
