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.
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
Route | Methods |
---|---|
/Users | POST |
/Users/:id | GET |
/Users/:id | PUT, PATCH |
/Users/:id | DELETE |
Push Groups and Group Memberships
Route | Methods |
---|---|
/Groups | POST |
/Groups/:id | GET |
/Groups/:id | PUT, PATCH |
/Groups/:id | DELETE |
Handle SCIM API Requests
Make sure to initialise the express body parser middleware to handle SCIM Content-Type headers such as application/scim+json
.
app.use(express.json({ type: ["application/*+json", "application/json"] }));
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,
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.