Skip to main content


This guide explains how to connect the SAML SSO to an Express.js app using SAML Jackson.

Visit the GitHub repo to see the source code for the Express.js SAML SSO integration.

Add SAML SSO to your app

Let’s start by building the SAML SSO authentication workflow into your Express.js app.

Install SAML Jackson

npm i --save @boxyhq/saml-jackson

Initialize SAML Jackson

const opts = {
externalUrl: '',
samlAudience: '',
samlPath: '/sso/acs', // The path to the SAML ACS endpoint
db: {
engine: 'mongo',
url: 'mongodb://localhost:27017/my-cool-app',

let oauthController;

// 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.
async function init() {
const ret = await require('@boxyhq/saml-jackson').controllers(opts);

oauthController = ret.oauthController;

Add route to initiate SSO

The authenticate flow begins with redirecting your user to the authorize URL. The response contains the redirect_url to which you should redirect the user.

router.get('/sso/authorize', async (req, res) => {
const redirectURI = ''; // The callback URI the app should redirect to after the authentication
const state = ''; // Create a random state and store on your app
const tenant = ''; // The tenant ID of the tenant you want to authenticate against
const product = ''; // The product ID of the product you want to authenticate against

const { redirect_url } = await oauthController.authorize({
tenant: tenant,
product: product,
redirect_uri: redirectURI,
state: state,


Add route to handle response from IdP

Add a route to handle the SAML Response from IdP.'/sso/acs', async (req, res) => {
const { SAMLResponse, RelayState } = req.body;

const { redirect_url } = await oauthController.samlResponse({
SAMLResponse: SAMLResponse,
RelayState: RelayState,


Add route to handle the callback

Add the route to handle the redirect endpoint which will handle the callback after a user has authenticated. This endpoint should exchange the authorization code with the authenticated user's profile.

router.get('/sso/callback', async (req, res) => {
const { code, state } = req.query;

const tenant = ''; // The tenant ID of the tenant
const product = ''; // The product ID of the product

const clientID = `tenant=${tenant}&product=${product}`;
const clientSecret = 'dummy';

// TODO: Verify the `state` matches the state you stored on your app

const { access_token } = await oauthController.token({
code: code,
client_id: clientID,
client_secret: clientSecret,

const profile = await oauthController.userInfo(access_token);

// You can use the `profile` information for further business logic.


Next steps