Skip to main content

Express.js

This guide will teach you to implement SCIM Provisioning in your Express.js app.

Quickstart

Directory sync helps organizations automate the provisioning and de-provisioning of their users in the Enterprise SaaS app. As a result, it streamlines the user lifecycle management process by saving valuable organizational hours, creating a single truth source of the user identity data.

Install SAML Jackson

Let’s start by installing SAML Jackson to your Express.js app.

npm i --save @boxyhq/saml-jackson

Initialize SAML Jackson

Please note that the initialization of @boxyhq/saml-jackson is async, you cannot run it at the top level. Run this in a function where you initialize the express server.

let directorySyncController;

const opts = {
externalUrl: `http://localhost:3000/`,
samlPath: '/',
scimPath: '/api/scim',
db: {
engine: 'sql',
type: 'postgres',
url: 'postgres://username:password@localhost:5432/your-database-name',
},
};

async function init() {
const ret = await require('@boxyhq/saml-jackson').controllers(opts);

directorySyncController = ret.directorySyncController;
}

Create the Directory

The first step towards the integration is creating a directory for a tenant.

Directory Sync providers (Identity Providers) require you to provide a SCIM Base URL and SCIM Auth token. Both are unique for each directory your app users create.

const { data, error } = await directorySyncController.directories.create({
name: 'any-name',
type: 'okta-scim-v2',
tenant: "tenant-identifier"
product: "product-identifier"
});

The response will looks like as below:

{
"data": {
"id": "58b5cd9dfaa39d47eb8f5f88631f9a629a232016",
"name": "any-name",
"tenant": "tenant-identifier",
"product": "product-identifier",
"type": "okta-scim-v2",
"log_webhook_events": false,
"scim": {
"path": "/api/scim/58b5cd9dfaa39d47eb8f5f88631f9a629a232016",
"secret": "IJzAoevjD_liiiy-VkDtXg",
"endpoint": "http://localhost:5225/api/scim/58b5cd9dfaa39d47eb8f5f88631f9a629a232016"
},
"webhook": {
"endpoint": "",
"secret": ""
}
},
"error": null
}

Note the keys scim.endpoint and scim.secret from the above JSON. Your users need these values while configuring SCIM app on their Identity Provider.

Typically you'll have to provide some UI where the users can create the directory for their providers and display SCIM Base URL and SCIM Auth Token for the directory the user created. Usually, this UI comes under organization settings or team settings.

For example, see the demo below.

create-directory

You can retrieve the supported list of Directory Sync providers by calling the method directorySyncController.providers().

Understand SCIM API Requests

A key piece to implementing SCIM is building a RESTful API that IdP's SCIM provisioning can call to provision users and groups to your app. The requests will come to the SCIM Base URL (scim.endpoint).

Here are the calls your API should be able to receive from IdP SCIM provisioning for a given SCIM Base URL (scim.endpoint).

Users Provisioning

RouteMethods
/UsersPOST
/Users/:idGET
/Users/:idPUT, PATCH
/Users/:idDELETE

Push Groups and Group Memberships

RouteMethods
/GroupsPOST
/Groups/:idGET
/Groups/:idPUT, PATCH
/Groups/:idDELETE

Handle SCIM API Requests

Now let's add the route to handle the incoming requests from the Directory Sync providers.

router.all(
'/api/scim/:directoryId/:resourceType/:resourceId?',
async (req, res, next) => {
const { params, method, body, headers, query } = req;
const { directoryId, resourceType, resourceId } = params;

const authToken = headers.authorization.split(' ')[1];

// Construct the event
const request = {
method: method,
body: body ? JSON.parse(body) : undefined,
directoryId: directoryId,
resourceId: resourceId,
resourceType: resourceType.toLowerCase(),
apiSecret: authToken,
query: {
count: query.count ? parseInt(query.count) : undefined,
startIndex: query.startIndex ? parseInt(query.startIndex) : undefined,
filter: query.filter,
},
};

// Handle the requests
const { status, data } = await directorySyncController.requests.handle(
request,
async (event) => {
console.log(event); // Do something with the event
}
);

// Send the response back
return res.status(status).json(data);
}
);

router.all('/api/scim/:directoryId/:resourceType/:resourceId?', async (req, res, next) => {...}) is a catch all paths route. Matched parameters will be sent as a parameter to the route.

Look at the highlighted lines, and you can pass an async callback method to the directorySyncController.requests.handle as a second argument. This method will be called with SCIM event as the first argument.

Checkout the documentation for SCIM events and Types to understand more about the events.

Use these events to trigger actions in your application, such as creating a new user in your application, or updating a user in your application based on the changes made in the directory.

Configure the Identity Provider

Your users should typically do this step at their end. We've detailed documentation for each Directory Sync provider.