Fetching Encrypted values in 𝖢̶𝗈̶𝗇̶𝗇̶𝖾̶𝖼̶𝗍̶𝗈̶𝗋̶ Cloud Rule

Hello,

I am trying to fetch the client Id and client secret from the connector attributes in source. Due to security reasons, I want to encrypt them, and I did it in the same way, as detailed in the below post.

Best Practice for Securing API Credentials in Web Services Connector After Rule - Identity Security Cloud (ISC) / ISC Discussion and Questions - SailPoint Developer Community

Source Config:

image

Rule Config:

String cc_clientID = (String) application.getAttributeValue("cc_clientID");
String cc_clientSecret = (String) application.getAttributeValue("cc_clientSecret");
String cc_refreshToken = (String) application.getAttributeValue("cc_refreshToken");
String cc_tokenURL = (String) application.getAttributeValue("cc_tokenURL");
String cc_identityURL = (String) application.getAttributeValue("cc_identityURL");

I am getting the encrypted value in the rule, and I am unable to use that encrypted value in my connector rule. Attaching logger for reference:

Mar 9, 2026 @ 18:28:56.458	Token API failed: {"error":"invalid_client","concur-correlationid":"4a71bc486aaaabce24a0c62fc8244bddf4","error_description":"client_id 3_{eyJjdHkiOiJwbGFpbi90ZXh0IiwiWC1TUC1Qb2xpY3kiOnsiYWxsb3dlZFNvdXJjZXMiOlsiM2MyY2YyMjI1ZGNiNDBjYmJlNjYzYjkwN2M1ZDMyZjAiXX0sImVuYyI6IkEyNTZDQkMtSFM1MTIiLCJhbGciOiJSU0EtT0FFUC0yNTYifQ.ThP174fqhwQm0La3SUKAxft9aKyGqWVw2gNKlyW7fshSe51vrHC6MCneWH_V5pqKgI-XifDNUg7bzx0pOrh1C9tTYvwuTKiDziIK82QB5N0zrWzn_wvx5OLXiYTLcM-XJemCTzi5_sXS3ODM81afikP1cAc3ojutrdEHgIs75asJ4RH2cBW2gXCPzwRa6pkh0l62s1--Qs6oyuuqLFPRwQ-3a5QeeFQsUBd94wuAOzMjdrKXF_MDSPdLYpMZyz2A4TyNNGlGp257oJclZLQE8bsanJ4uIYB8itngp4wvf7tjH6YxV_rzwwPLPGo2E_NO47FZ01AyEHnNc5TCx_yJ-A.OLKTJthj22QAp2XAs3-Zgg.fWk66Iqdm0V1mBGx4QtfxZmSbcW8pRmp0RDlAL5H8b6liDkBln8aEJqvmLw85fQM.YBnSwaSpcIWM6cNhX8A84n7daURA1cL5WPuierJ6fhE} is not known to us","code":61,"geolocation":"https://domainnnn"}

Please review and let me know if I am making any issues in this config.

Thank you.

Hi @sarvanmarri

Try passing the secrets through your header in your operation then referencing them using .getHeader() & headers.get(“VARIABLE_NAME”). This is the way I’ve been passing in custom secret values from the source and has worked well for me.

Headers In HTTP Operation:

Key → Value

cc_tokenURL → $application.cc_tokenURL$

Rule:

Map headers = requestEndPoint.getHeader();

String cc_tokenURL = (String) headers.get("cc_tokenURL”);

Let me know if this works for you or if you have any questions!

Try adding the values again in the source json. I have seen such issues in the past which was fixed by doing so

Don’t you need the attribute name to have a “_CA” suffix, AND, that it is listed in the “encrypted” attribute list?

My guess is that the getAttributeValue doesn’t know it needs to decrypt the ciphertext unless the attribute name ends in _CA.

This is a best practice so you can differentiate between Custom Attributes (_CA) and the out of the box encrypted values like private_key or accesstoken.

It’s not required to use _CA to have the value be encrypted in the source or be able to pull it dynamically. As long as the variable is included in the list of encrypted values & in the connectorAtributes section of the source, it will encrypt and be able to be pulled properly.

LoL…documentation is bluffing then I guess:
image

…with scare tactics:
image

I wish the documentation doesn’t colour what’s needed / not needed.

yeah lol, not accurate. I think they are just warning if you use some of the reserved attribute names that are out of the box, you will have issues, which is true.

You need a better model for your answer.

I don’t think context object is available to Connector rules. And, SailPoint does not deploy any cloud rules using context object as it’s not allowed in ISC anymore.

Yeah, Deepak’s answer (now removed) came from a model hallucinating between IIQ and ISC.

Hi @trettkowski ,

I am currently not using the webservices connector. I am using this rule as an attribute generator in create provisioning policy.

Source: Sap Concur SaaS

Can you suggest any other way to overcome my issue?

Hi @David_Norris ,

I have tried this approach with no luck. Still, it is unable to decrypt the value.

Tried this again by clearing all the attributes and adding again, no luck.

Need your particular procedure execution details to see where the gap is. Help us to help you. We can’t see how you did what you did.

It’s simple.

As mentioned, I have added additional attributes in to source > connector attributes like below:

“refreshToken_CA”: “zzzzz”,
“clientID_CA”: “zzzzz”,
“clientSecret_CA”: “zzzzz”,
“tokenURL_CA”: “zzzzz”,
“identityURL_CA”: “zzzzz”,

After adding these attributes from visual studio, I have saved my source.

Now, I have added the below attributes into the encryped attribute

“encrypted”: “refresh_token, client_secret, oauth_token_info, cc_refreshToken, cc_clientID, cc_clientSecret, refreshToken_CA, clientID_CA, clientSecret_CA”

Below is my attributeGenerator code:

String CLIENT_ID = (String) application.getAttributeValue(“clientID_CA”);
String CLIENT_SECRET = (String) application.getAttributeValue(“clientSecret_CA”);
String REFRESH_TOKEN = (String) application.getAttributeValue(“refreshToken_CA”);
String TOKEN_URL = (String) application.getAttributeValue(“tokenURL_CA”);
String IDENTITY_URL = (String) application.getAttributeValue(“identityURL_CA”);

I am fetching the encrypted values in the logs, instead of decrypted values.

Mar 12, 2026 @ 19:24:32.492 CLIENT_ID :: 3_{eyJjdHkiOiJwbGFpbi90ZXh0IiwiWC1TUC1Qb2xpY3kiOnsiYWxsb3dlZFNvdXJjZXMiOlsiM2MyY2YyMjI1ZGNiNDBjYmJlNjYzYjkwN2M1ZDMyZjAiXX0sImVuYyI6IkEyNTZDQkMtSFM1MTIiLCJhbGciOiJSU0EtT0FFUC0yNTYifQ.D-BKV74ICl4UV2vpvpIOfgGqdaqtHwZUJZLX_YWVHz-0gt_cCTwy_1iT8ChSqihbRKGkDr5Sa74Hbop6gKhVHKPnu2gdCSOqo8iipOHAyPh5iyGxA2FYWJ5QSX7A4cbR1SaaSCyOT70fg69CpMBWh_0HUkpo9cE8VqLEPrS697K8J6WFiJepdcZBuE2KbXaD0qk1OaA-Xu4Wy9li7knj2XC026ICiOxj4c4E6EPwUGpWE2ef6HC0_xYTVVFxr02hOveQCOUicIEjguInPq8SkDbsZYGkexBaboCTsi2JfKZSjRLAZ8Wkk0y8cdpR7yjJSMgcTtv4EOUNaQqAaczwGQ.w9tItHTmW6CZq5_YPmikrA.fNZcW9euJXtt8VBNQlzSQ1-ttjzN0xKzjmN2wWCBzqln3p5nBtp1B0Y5Q2GPA92S.rmlZboKJ18hz5Usq7eBi5tA8m2KtWomleLlYGjTzoX4}

Let me know if you need any additional information.

I can see, it is very straight forward as per the other posts. But I don’t know why, it became complex in my situation.

Based on the sequence / order of these two steps, your *_CA attribute values would still be in clear text, not ciphertext.

The encryption process occurs as value enters into the attribute, AFTER you’ve specified / added the _CA attribute into the encrypted list.

Exactly my thought and this is why I asked him to reenter the values , but apparently that did not work too.

In fact, as you can see in the log, values are encrypted, but application.getAttributeValue() is not returning the decrypted values in the Connector rule. I have used this several times and have not had this issue. Probably a ticket to SP Support?

@sarvanmarri can you share the entire Connector rule?

My final attributes in source are looking like this. Is this the expectation?

image

FYI, I am not encrypting the URL attributes, as those are not sensitive information. I am just looking to encrypt the client credentials, which is failing to decrypt.

yeah, I agree to you, this issue became weird as it is only not working for me.

My attribute generator rule:
(for some reason, I kept all the loggers to error level, please ignore it)

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.NameValuePair;
import java.util.List;
import java.util.ArrayList;


import sailpoint.object.Attributes;
import sailpoint.object.Application;

log.error("Rule Started :: SAP CONCUR EMPLOOYEEID GENERATION");	

sailpoint.object.Attributes attributes = application.getAttributes();
log.error("attributes in getAttributes :: "+attributes.toString());

String CLIENT_ID = (String) application.getAttributeValue("clientID_CA");
String CLIENT_SECRET = (String) application.getAttributeValue("clientSecret_CA");
String REFRESH_TOKEN = (String) application.getAttributeValue("refreshToken_CA");
String TOKEN_URL = (String) application.getAttributeValue("tokenURL_CA");
String IDENTITY_URL = (String) application.getAttributeValue("identityURL_CA");

log.error("CLIENT_ID :: "+CLIENT_ID);
log.error("TOKEN_URL :: "+TOKEN_URL);
log.error("IDENTITY_URL :: "+IDENTITY_URL);

int PAGE_SIZE = 100;

String empType = identity.getAttribute("employeeType");


public String generateAccessToken() {
  try {

    HttpClient client = HttpClients.createDefault();
    HttpPost post = new HttpPost(TOKEN_URL);

    List params = new ArrayList();

    params.add(new BasicNameValuePair("client_id", CLIENT_ID));
    params.add(new BasicNameValuePair("client_secret", CLIENT_SECRET));
    params.add(new BasicNameValuePair("grant_type", "refresh_token"));
    params.add(new BasicNameValuePair("refresh_token", REFRESH_TOKEN));

    post.setEntity(new UrlEncodedFormEntity(params, "UTF-8"));
    HttpResponse response = client.execute(post);
    int status = response.getStatusLine().getStatusCode();
	
    if(status != 200) {

      String error = EntityUtils.toString(response.getEntity());
      log.error("Token API failed: " + error);
	  
      return null;
    }
    String json = EntityUtils.toString(response.getEntity());
	
    return extractAccessToken(json);
  }
  catch(Exception ex) {
    log.error("Token generation failed: " + ex);
    return null;
  }
}

public String extractAccessToken(String json) {

  String key = "\"access_token\":\"";
  int start = json.indexOf(key) + key.length();
  int end = json.indexOf("\"", start);
  
  return json.substring(start, end);
}

public int extractTotalResults(String json) {

  String key = "\"totalResults\":";
  int start = json.indexOf(key) + key.length();
  int end = json.indexOf(",", start);

  if(end == -1)
    end = json.indexOf("}", start);
	
  return Integer.parseInt(json.substring(start, end).trim());
}


public String[] extractEmployeeNumbers(String json) {

  String key = "\"employeeNumber\":\"";
  List list = new ArrayList();
  int index = 0;

  while((index = json.indexOf(key, index)) != -1) {

    int start = index + key.length();
    int end = json.indexOf("\"", start);
    list.add(json.substring(start, end));
    index = end;
  }
  return (String[]) list.toArray(new String[list.size()]);
}

public String generateHRMSEmployeeID(String empId) {

  while(empId.length() < 8) {
    empId = "0" + empId;
  }
  return empId;
}

public String generateOutSourceEmployeeID() {

  try {

    String accessToken = generateAccessToken();

    if(accessToken == null)
      return null;

    HttpClient client = HttpClients.createDefault();
    int startIndex = 1;
    int totalResults = Integer.MAX_VALUE;
    String maxEmployeeNumber = "900000";


    while(startIndex <= totalResults) {

      String resourceUrl =
        IDENTITY_URL +
        "?count=" + PAGE_SIZE +
        "&startIndex=" + startIndex;

      HttpGet httpGet = new HttpGet(resourceUrl);
      httpGet.setHeader("Authorization", "Bearer " + accessToken);
      httpGet.setHeader("Accept", "application/json");
      HttpResponse response = client.execute(httpGet);
      int status = response.getStatusLine().getStatusCode();

      if(status != 200) {
        String error = EntityUtils.toString(response.getEntity());
        log.error("Identity API failed: " + error);
        return null;
      }

      String body = EntityUtils.toString(response.getEntity());
      totalResults = extractTotalResults(body);
      String[] employeeNumbers = extractEmployeeNumbers(body);

      for(int i=0; i<employeeNumbers.length; i++) {

        String emp = employeeNumbers[i];
		
        if(emp.startsWith("900")) {
		
          if(emp.compareTo(maxEmployeeNumber) > 0) {
            maxEmployeeNumber = emp;
          }
		  
        }
      }
      log.error("Processed startIndex: " + startIndex +
                " maxEmployeeNumber: " + maxEmployeeNumber);
      startIndex += PAGE_SIZE;
    }
    log.error("Final maxEmployeeNumber: " + maxEmployeeNumber);
    return maxEmployeeNumber;
  }
  catch(Exception ex) {
    log.error("Identity fetch failed: " + ex);
    return null;
  }
}

String empId = identity.getAttribute("identificationNumber");

if(empType != null && empType.equalsIgnoreCase("Outsourced")) {
  String id = generateOutSourceEmployeeID();
  if(id != null) {
    int idNum = Integer.parseInt(id);
    idNum++;
    return String.valueOf(idNum);
  }
  return null;
}

return generateHRMSEmployeeID(empId);

Think that’s the problem… Don’t think it can work from the attribute generator rule. It’s meant to work from the webservice before / after operation rules. (Attribute generator rules are not considered as Connector Rule, because the generation happens on ISC before passing down to a connector for provisioning)