Creating an Account Without Granting Real Access Using a Non-Persistent Entitlement

High-Level Understanding

Use case

In some scenarios, the requirement is to create the account only, without granting any real access in the target system. We don’t want to add a real dummy group on the target system.


Current Limitation

Currently, there is no way to create an account in a source without going through an access request.

To trigger account creation, an entitlement must be granted.


Common Workaround

Create a dummy entitlement that is granted to the user.

Even though it does not grant any real permissions, this entitlement still exists as a real entitlement, which is not always desirable from a governance or design perspective.


Proposed Solution

The proposed solution still uses an entitlement to trigger account creation, but:

  • The entitlement does not actually exist in the target system

  • It is used only as a technical trigger inside SailPoint

To achieve this, a second entitlement type is created, dedicated exclusively to account creation.

This entitlement will never be effectively granted in the target system.


How the Mechanism Works

  • A special entitlement is granted via an Access Profile

    • Note: In SailPoint ISC, roles and entitlements are sticky, meaning that if access is granted through ISC, it will be re-provisioned if removed from the target system. To allow the access to be removed after the next aggregation, you should use an Access Profile instead.
  • This entitlement triggers the account creation

  • During the first account aggregation:

    • The entitlement is not found on the account

    • The Access Profile is automatically removed from the identity

  • No reprovisioning loop occurs because:

    • Access Profiles do not enforce access

    • Unlike Roles or enforced entitlement requests


Implementation Steps

  1. Create a new entitlement type
    Example: create_account

  2. Add a new attribute to the account schema associated with this entitlement type
    Example: Attribute name fake_fake, Type create_account

  3. Reset the source using the VS Code plugin (optional)

    • This is only required if the new attribute was added to a running source. In some cases, this new attribute may not be recognized as an entitlement when you create and configure the Access Profile.

  4. Create an HTTP operation for Group Aggregation-create_account

    • cURL:

      curl --location --request GET 'https://postman-echo.com/get?id=fake_entitlement&description=fake_description'
      
    • Note: This postman-echo.com allows you to send data and receive the same data back, simulating an entitlement aggregation response.

    • Mapping:

  5. Create an HTTP operation for Add Entitlement-create_account

    • cURL:

      curl --location --request GET 'https://postman-echo.com/get?id=fake_entitlement&description=fake_description'
      
    • Note: This endpoint exists only to trigger the provisioning flow, and since it always returns a 200 response, the execution will be considered successful.

  6. Execute entitlement aggregation for the create_account entitlement type

    You should see something like this

  7. Create an Access Profile (example)

  8. Request the Access Profile

    The account will receive that fake entitlement, but it will disappear during the next aggregation.

  9. Run account aggregation (manually or scheduled)


Final Notes

  • The account is successfully created

  • No dummy entitlement remains assigned

  • No enforcement or reprovisioning loop occurs

  • The identity remains clean after the process

  • This access will not cause any issues during access certification

  • You can use Workflows to automatically grant this Access Profile

7 Likes

Hello @moises_proof ,

Thank you for sharing this interesting approach—it addresses a known limitation in ISC when the goal is to manage accounts (particularly account creation) without managing access.

I have a question regarding your entitlement aggregation simulation. You used the following request:

GET https://postman-echo.com/get?id=fake_entitlement&description=fake_description

In a restricted environment using a VA-based Webservice connector, where outbound communication to external URLs is limited or blocked, do you have any idea for simulating this entitlement aggregation?

That piece caught my eye as well. It creates a dependency on postman-echo. I would think that in your restricted environment, that it could still reach ISC, so curl --location --request GET ‘https://<tenantName>.identitynow.com’ would still get you a 200 response code. Then you would need to use a short after operation rule to set your entitlement name and id, since it won’t be in the response.

2 Likes

Hi @baoussounda and @MattUribe,

Thank you both for the feedback, these are very valid points.

I gave it some thought and there is indeed a cleaner and more self-contained approach that avoids any external dependency and eliminates the need for an after rule (especially if you want to work with a SaaS-based connector).


Alternative approach (no external endpoint required)

Instead of relying on an external service like Postman Echo, you can leverage ISC itself as the source of truth.

Steps

1. Create a dummy Role

Create a Role with the same name as the group you want to simulate
Populate the description field as needed
The role does not need to be enabled, it just needs to exist

Example:


2. Use ISC API as the aggregation source

You can call the ISC Roles API directly:

curl --location --request GET "https://<tenant>.api.identitynow.com/v2025/roles/<role_id>"

Example:

curl --location --request GET "https://devrel-ga-12345.api.identitynow-demo.com/v2025/roles/44a67e41e3804e058c4338bf37f09035"

Where:

  • <role_id> is the ID of the role you created

Example:


3. API Response

The API will return the role details, which can then be used to simulate the entitlement:

Example:


4. Mapping

Map the fields accordingly in your Web Services connector:

Example:


5. Important note (one-time aggregation)

  • This aggregation only needs to be executed once
  • To simplify the setup, you can temporarily hardcode an Authorization header
  • After the first successful aggregation, you can remove the hardcoded token

Example:

6. Entitlements (result)

This is what should appear after run the group aggregation.

Example:

this below logic may work as well. I am in early testing with it. It is a web services after operation rule to add a group entitlement as part of aggregating. Look for the values in the code " and replace it with your application name. Would just need to reference this rule on the afterrule parameter for the HTTP Operation.

I modified my group schema to be:


{

    "nativeObjectType": "group",

    "identityAttribute": "groupId",

    "displayAttribute": "groupName",

    "hierarchyAttribute": null,

    "includePermissions": false,

    "features": [],

    "configuration": {},

    "attributes": [

        {

            "name": "groupId",

            "nativeName": null,

            "type": "STRING",

            "schema": null,

            "description": "Group ID",

            "isMulti": false,

            "isEntitlement": false,

            "isGroup": false

        },

        {

            "name": "groupName",

            "nativeName": null,

            "type": "STRING",

            "schema": null,

            "description": "Group Name",

            "isMulti": false,

            "isEntitlement": false,

            "isGroup": false

        }

    ],

    "id": "e86073f554954007a5696fb5eba8be40",

    "name": "group"

}
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
 
log.info("<app name> Append Marker Entitlement: received "
    + (processedResponseObject != null ? processedResponseObject.size() : 0)
    + " rows from <app name>");
 
List outRows = new ArrayList();
Set seenGroupIds = new HashSet();
 
// Pass through real groups, deduping on groupId
if (processedResponseObject != null) {
    for (Iterator it = processedResponseObject.iterator(); it.hasNext(); ) {
        Map row = (Map) it.next();
        if (row == null) continue;
 
        Object gid = row.get("groupId");
        String gidKey = (gid != null) ? gid.toString() : null;
 
        if (gidKey == null || seenGroupIds.add(gidKey)) {
            outRows.add(row);
        }
    }
}
 
int realGroupCount = outRows.size();
 
// Append the synthetic marker entitlement
Map marker = new HashMap();
marker.put("groupId", "<app name>_Default_Access");
marker.put("groupName", "<app name>_Default_Access");
outRows.add(marker);
 
log.info("<app name> Append Marker Entitlement: emitting "
    + realGroupCount + " unique real groups + 1 marker = "
    + outRows.size() + " entitlements");
 
Map updatedInfoMap = new HashMap();
updatedInfoMap.put("data", outRows);
return updatedInfoMap;