SaaS Connector Tutorial for the Salesforce SCIM API

Introduction

While the SaaS Connectivity documentation includes simple examples for Airtable and Discourse here, several questions need to be answered when it comes to building a real-world, customer-ready, SaaS Connector.

In this blog, I will use Salesforce as an example, to illustrate some of the lessons learned and key tools I am using to build and maintain customer-ready SaaS Connectors. Let’s keep in mind that Salesforce is used only as an example here, and the objective is not to promote this connector as a usable SaaS Connector.

Exploring the OOTB Salesforce Connector

First, we need access to a Salesforce instance. It is possible to signup for a developer account and obtain a free developer instance here. Then we need to enable a user account, for example via its profile, for API access(API enabled). See information about Salesforce and SCIM here.

There are already 2 Sources in ISC (Identity Security Cloud, the new name for IdentityNow) for Salesforce, including Salesforce SaaS, which does not require a VA. Again, our goal is not to replace those Sources, but to illustrate some of the concepts we need to apply to eventual Sources for which we want to create a SaaS Connector.

Just as a test, let’s create a Source for Salesforce using the SCIM 2.0 SaaS Connector:


SCIM 2.0 SaaS Connector Settings for Salesforce.


We should be able to successfully test our settings.

Now, let me refer to a tool I introduced in a previous article here.


apiShark: AWS Lambda Function tool.

This tool, based on an AWS Lambda Function, allows us to monitor all Requests and Responses between ISC and Salesforce SCIM API, using AWS CloudWatch. I am using this tool every time I need to know the specific Requests sent by ISC and the associated Responses. Here we can see that the Test Connection for the SCIM 2.0 SaaS Source is using GET /Users:


Test Connection monitoring.

We can also use the tool to monitor other types of actions, for example Entitlement Aggregation:


Entitlement Aggregation: Request.


Entitlement Aggregation: Response.

This tool, which I am calling apiShark, comes very handy to complement what we can see in the logs, and to assist with troubleshooting SaaS Connectors while we are building them. It helps confirm the expected Requests are actually happening, together with the expected Responses.

An Example SaaS Connector for Salesforce

Now let’s take a look at the SaaS Connector example for SalesForce. The code and a Postman Collection can be downloaded
tutorial-scim-salesforce.zip (29.2 KB)

The SaaS Connector file structure is based on the Discourse example:


SaaS Connector: File structure and index.ts.


Account List and re-usable util function to format Response for ISC.


Re-usable util function includes both Groups and Entitlements as 2 Group types.


2 util functions are used to gather Groups and Entitlements as 2 Group types for ISC.


connector-spec.json schemas for entitlements include 2 Group types.

Using Global Variables

One important thing to know about SaaS Connectors is that they are implemented as long-lived Containers, which allows us to leverage Global Variables, for example to store a Bearer Token that we can re-use between calls. We also use Global Variables to share URLs and credentials between functions.


Global Variables are declared in types/global.d.ts.

To avoid having to re-authenticate for every Request made by ISC via the SaaS Connector, we can check if the Bearer Token we have stored in Global Variables is still valid and not expired:


Leveraging a Function to check if the Token is valid or expired.

All functions are located within scim-functions.ts, including check_token_expiration and scim_auth:


Function: check_token_expiration.


Function: scim_auth calculates expiration time and store in Global Variable.

Note that the Authentication Token API response from Salesforce does not include the expiration_time attribute, so we need to calculate it in milliseconds based on issued_at attribute, knowing that the default expiration time for Salesforce is 2 hours.


Salesforce Authentication Token response.

The scim-functions.ts file includes many examples of SCIM calls, including some that are generic like GET with Pagination:


Function: Generic GET with Pagination support.

Note that Pagination works differently for /Users vs /Groups and /Entitlements, so we have to adjust the use of calls with and without Pagination.

Uploading the SaaS Connector to ISC

Now let’s take a look at the SaaS Connector once uploaded to ISC. We should be able to create a new Source using the SaaS Connector:


Create a new Source using the SaaS Connector.


SaaS Connector: Entitlements for Salesforce Groups and Entitlements.


SaaS Connector: Salesforce Group entitlements can be made requestable and added to Access Profiles.

Salesforce requires an Entitlement at Account Creation time, for example for Standard vs Chatter Only User.


Provisioning Policy: Entitlement is required for Account Creation.

Error Handling

A very important aspect is Error Handling. Without proper handling of errors, we are exposing the ISC Users to cryptic Axios module errors. Instead, we want to be able to provide either the detailed Error Response obtained from Salesforce, or at a minimum a customizable error message that can sometimes help avoid confusion and customer Support tickets.


Try Catch block in my-client.ts that sends the error to smart_error_handling function.


Smart error handling function in scim-functions.ts.

For example, if we are using a developer instance for Salesforce, we are limited to 10 User accounts. Once we have reached the limit, if we try to create a new accounts, for example via ISC Access Request, Smart Error Handling allows us to know exactly why the operation fails:


Smart Error Handling example for POST /Users (Create Account).

Conclusion

I hope that you found this blog post and the attached example useful, and that it will give you some ideas on how to build your own customer-ready SaaS Connector.

6 Likes

Thanks for sharing this connector, helped me a lot. May I offer one minor improvement to your paging logic?

I ran into an API that throws an index out of bounds exception of you reference a page with no data (this is a very poorly built vendor API btw).

Instead of a Math.trunc +1, adding a page that may be have zero records (say your page size is 10 and you have 10 records), do a Math.ceil to get the exact number of pages required.

replace this

    var accounts = res.data.Resources
    const numberOfRecords = res.data.totalResults
    const itemsPerPage = 10
    const startIndex = res.data.startIndex
    const lastPage = Math.trunc(numberOfRecords / itemsPerPage) + 1

with this

var accounts = res.data.Resources;
const numberOfRecords = res.data.totalResults;
const itemsPerPage = 10;
const startIndex = res.data.startIndex;
const lastPage = Math.ceil(numberOfRecords / itemsPerPage);