Application accounts password management

Title: Enforce different passwords across environments for the same application accounts (IIQ 8.4)

Product and Version:
- SailPoint IdentityIQ 8.4

Description:
Following new security directives in our organization, we must ensure that users do NOT reuse the same password for the same application across different environments (e.g., DEV, TEST, UAT, PROD).

Concretely:
- A user may have multiple linked accounts for the same application, one per environment (e.g., APP_DEV, APP_TEST, APP_PROD).
- When the user (or a helpdesk process) changes the password of an account in one environment, IdentityIQ should reject the change if the new password matches the password of the same user’s account in any other environment for that application.

Requirement:
- During a password change or reset in IIQ, the password must be validated not only against the standard password policy, but also against passwords of:

  • The same user
  • For the same application
  • In other environments (e.g., APP_DEV vs APP_TEST vs APP_PROD)
    - If the new password is equal to an existing password in another environment, IIQ must:
  • Reject the request, and
  • Return a clear error message to the user (e.g. “This password is already used for your account in another environment. Please choose a different password.”).

Current behavior:
- Standard IIQ password policies (complexity, history, etc.) are working as expected on each individual application/environment.
- There is currently no cross-environment check: IIQ does not prevent users from setting the same password on APP_DEV, APP_TEST, and APP_PROD accounts.

Question / Assistance needed:
- What is the recommended way in IdentityIQ 8.4 to enforce this type of cross-environment password uniqueness?
- Is there any out-of-the-box feature or configuration (Password Policy, rule hooks, etc.) that allows:

  • Comparing the new password against the passwords of other linked accounts (same application, different environment)?
    - If custom development is required:
  • At which extension point should we implement this (e.g., password policy rule, BeforeProvisioning / BeforePasswordChange rule, custom validator)?
  • Are there best practices or example rules for cross-account password comparison?

Thank you in advance for your guidance on how to implement this security requirement in IIQ 8.4.

hi @chaoukiaddad

There is no out‑of‑the‑box functionality in IIQ 8.4 to enforce password uniqueness across environments for the same application.

I would try this solution:
Implementing a Before Password Change / Reset Rule in “LCM Manage Password workflow”.
Useful links: https://uploads.teachablecdn.com/attachments/CnaCoGdxR9K8wcM3ixBQ_Rules_in_IdentityIQ_7.2.pdf

Password management - IdentityIQ (IIQ) / IIQ Discussion and Questions - SailPoint Developer Community

This rule can:

  • Access the new password in clear text

  • Iterate over the user’s linked accounts (APP_DEV / TEST / UAT / PROD)

  • Compare against existing passwords (via target/system validation)

  • Reject the request with a custom error message if a match is found

Standard password policies work per application only and cannot do cross‑environment checks.

@haideralishaik What’s the rule type for Before Password Change Rule? I am not able to find it in the document. Seems like an incorrect reference, could you please check and confirm.

hi @neel193
Good catch thanks for calling that out. Let me clarify

There is no standalone rule type called “Before Password Change Rule” in IdentityIQ.
That name is shorthand people use, but it’s not an actual Rule type you will find in the UI.

In IIQ, cross‑password validation is implemented via custom logic in the “LCM Manage Password workflow”, typically using one of these:

  1. Workflow step with an embedded Script (most common)

    • Added before the password provisioning step

    • This script performs the validation and can block the request with an error message

  2. Rule called from the workflow script

    • Rule type is usually Generic (or Validation logic embedded directly in the workflow)

    • The workflow passes the new password, identity, and application context to the rule

There is no specific “Password Policy Rule” or “Before Password Change Rule” type that supports cross‑account comparison.

Thanks for pointing this out your interpretation is accurate :+1:

@chaoukiaddad Like explained above by @haideralishaik you can definitely handle this in LCM Manage Password workflow, but there is a downside of it when you throw the exception over there after validation, it doesn’t show the exact error in the UI. Instead it says that, password change failed and contact your administrator. We should figure out a way to throw the exact error message to the UI that will help the users.

Hello @neel193 @haideralishaik

thanks for your feedback, I tried to add a step in order to compare the recently updated pwd with the other pwds used in other apps, but seems that it oly compares with the the pwds used by the account for the same app (same env)
btw, i only specified one type of apps to check if it works (AD)

<Step icon="Analysis" name="CrossEnv Password Check" posX="772" posY="70">
    <Description>
      Check that the new password for any AD BRED-* account is not already
      used on another AD BRED-* account for the same identity.
    </Description>
    <Script>
      <Source><![CDATA[
import java.util.List;
import java.util.Map;

import sailpoint.api.SailPointContext;
import sailpoint.connector.Connector;
import sailpoint.connector.ConnectorFactory;
import sailpoint.object.Identity;
import sailpoint.object.Application;
import sailpoint.object.Link;
import sailpoint.object.ProvisioningPlan;
import sailpoint.tools.Util;

// Use a real SailPointContext for lookups
SailPointContext spctx = wfcontext.getSailPointContext();

// 'plan' and 'identityName' are workflow variables
if (plan == null || identityName == null) {
  return;
}

// Get the identity
Identity identity = spctx.getObjectByName(Identity.class, identityName);
if (identity == null) {
  return;
}

// Get all account requests from the plan
List acctReqs = plan.getAccountRequests();
if (Util.isEmpty(acctReqs)) {
  return;
}

// Preload links once
List links = identity.getLinks();
if (Util.isEmpty(links)) {
  return;
}

// Iterate through each AccountRequest
for (Object o : acctReqs) {

  ProvisioningPlan.AccountRequest ar = (ProvisioningPlan.AccountRequest) o;
  String appName = ar.getApplicationName();
  if (appName == null || !appName.startsWith("AD BRED-")) {
    continue; // only AD BRED-* apps
  }

  ProvisioningPlan.AccountRequest.Operation op = ar.getOperation();
  // Only process modify / change password type operations
  if (op != ProvisioningPlan.AccountRequest.Operation.Modify &&
      op != ProvisioningPlan.AccountRequest.Operation.ChangePassword) {
    continue;
  }

  // Native identity of the account whose password is being changed
  String targetAccountId = ar.getNativeIdentity();

  // ---- Get the new password from AttributeRequests (attr=password) ----
  List attrs = ar.getAttributeRequests();
  if (Util.isEmpty(attrs)) {
    continue;
  }

  String newPassword = null;
  for (Object ao : attrs) {
    ProvisioningPlan.AttributeRequest attr = (ProvisioningPlan.AttributeRequest) ao;
    String attrName = attr.getName();
    if ("password".equalsIgnoreCase(attrName)) {
      Object v = attr.getValue();
      if (v != null) {
        newPassword = v.toString();
      }
    }
  }

  if (Util.isEmpty(newPassword)) {
    // No password found in this AccountRequest, skip it
    continue;
  }

  System.out.println("### CrossEnv: new password set on app=" + appName
                     + ", nativeIdentity=" + targetAccountId
                     + ", pwdLen=" + newPassword.length());

  // ---- Check this password against all other AD BRED-* links ----
  for (Object lo : links) {
    Link link = (Link) lo;
    Application app = link.getApplication();
    if (app == null) {
      continue;
    }

    String otherAppName = app.getName();
    if (otherAppName == null || !otherAppName.startsWith("AD BRED-")) {
      continue;
    }

    String otherAccountId = link.getNativeIdentity();

    // Skip the exact same account we are changing
    if (targetAccountId != null && targetAccountId.equals(otherAccountId)) {
      continue;
    }

    // Determine the login to use for authenticate: sAMAccountName preferred
    String login = null;
    Map linkAttrs = link.getAttributes();
    if (linkAttrs != null) {
      Object sam = linkAttrs.get("sAMAccountName");
      if (sam != null) {
        login = sam.toString();
      }
    }
    if (login == null) {
      // Fallback on nativeIdentity (DN) if no sAMAccountName
      login = otherAccountId;
    }

    try {
      if (login == null) {
        continue;
      }

      // Get connector for this AD application
      Connector conn = ConnectorFactory.getConnector(app, null);

      // If auth succeeds, this password is already used on that account
      boolean authOk = conn.authenticate(login, newPassword);

      System.out.println("### CrossEnv: test login=" + login
                         + ", app=" + otherAppName
                         + ", authOk=" + authOk);

      if (authOk) {
        // Stop the workflow with a clear message for the user
        throw new RuntimeException(
          "The chosen password is already used on AD account '" 
          + login + "' (application '" + otherAppName +
          "'). Please use a different password for each AD BRED account."
        );
      }

    } catch (RuntimeException e) {
      // our own error: rethrow so it shows in UI
      throw e;
    } catch (Exception e) {
      // Technical log; do not block on connector errors (unless your policy requires)
      System.out.println("Error during cross-env password check for identity "
                         + identity.getName() + " on app " + otherAppName
                         + ": " + e);
    }
  } // end for links

} // end for acctReqs

return;
      ]]></Source>
    </Script>
    <Transition to="LCM Manage Password"/>
  </Step>

hi @chaoukiaddad Thanks for sharing the step and the script.

This behavior is expected.

Your workflow logic is correct, but the limitation is here:

conn.authenticate(login, newPassword)

authenticate() works per connector / per AD environment only. Even though you loop through multiple AD BRED-* links, each authentication is evaluated inside that specific AD instance. It cannot reliably validate the password against other AD environments unless cross‑domain authentication is supported.

So:

  • :white_check_mark: Works for the same app / same env

  • :cross_mark: Cannot truly enforce cross‑environment password uniqueness using connector authentication alone

To enforce this reliably, you need:

  • A central password validation service (PAM / LDAP proxy / custom API), or

  • An API that can validate credentials across all environments

This is a connector/backend limitation, not an issue with your script or workflow placement.

Hope this clarifies :+1:

@chaoukiaddad In the script, you’ll have access to context which can access all objects. You can check all apps account available on the user or you can also check application passwords or you can scan against password available on other users.

If you want to check in your lower instances (might not be a good practice as lower env have incorrect data sometimes), you need to expose an API in lower instance, which you can call in your Prod. With this approach you can achieve your requirement within IIQ modules only.

Finally, I was able to address this specific need using a rule that checks the password history in IIQ.