Skip to main content

NPM Library

SAML Jackson is available as an npm package that can be integrated into any Node.js based web application framework.

Install the SDK using the command below.

npm install @boxyhq/saml-jackson

Configuration Options

Please note that the initialization of @boxyhq/saml-jackson is async, you cannot run it at the top level.

import jackson, {
type IConnectionAPIController,
type IOAuthController,
} from '@boxyhq/saml-jackson';

let oauth: IOAuthController;
let connection: IConnectionAPIController;

(async function init() {
const jackson = await require('@boxyhq/saml-jackson').controllers({
externalUrl: 'https://your-app.com',
samlAudience: 'https://saml.boxyhq.com',
oidcPath: '/api/oauth/oidc',
samlPath: '/api/oauth/saml',
db: {
engine: 'sql',
type: 'postgres',
url: 'postgres://postgres:postgres@localhost:5432/postgres',
},
});

oauth = jackson.oauthController;
connection = jackson.connectionAPIController;
})();

Single Sign-On Connections

Create SAML Connection

Create a new SAML Single Sign-On connection.

await connection.createSAMLConnection({
tenant: 'boxyhq',
product: 'your-app',
rawMetadata: '<raw-saml-metadata>', // Visit https://mocksaml.com to download Metadata
redirectUrl: ['https://your-app.com/*'],
defaultRedirectUrl: 'https://your-app.com/sso/callback',
});

Update SAML Connection

Update a SAML Single Sign-On connection.

await connection.updateSAMLConnection({
tenant: 'boxyhq',
product: 'your-app',
rawMetadata: '<raw-saml-metadata>',
redirectUrl: ['https://your-app.com/*'],
defaultRedirectUrl: 'https://your-app.com/sso/callback-updated',
clientID: '<clientID of the SAML SSO Connection>',
clientSecret: '<clientSecret of the SAML SSO Connection>',
});

Create OIDC Connection

Create a new OIDC Single Sign-On connection.

await connection.createOIDCConnection({
tenant: 'boxyhq',
product: 'your-app',
redirectUrl: ['https://your-app.com/*'],
defaultRedirectUrl: 'https://your-app.com/sso/callback',
oidcDiscoveryUrl:
'https://accounts.google.com/.well-known/openid-configuration',
oidcClientId: '<OpenID Client ID>',
oidcClientSecret: '<OpenID Client Secret>',
});

Update OIDC Connection

Update an OIDC Single Sign-On connection.

await connection.updateOIDCConnection({
tenant: 'boxyhq',
product: 'your-app',
redirectUrl: ['https://your-app.com/*'],
defaultRedirectUrl: 'https://your-app.com/sso/callback',
oidcDiscoveryUrl:
'https://accounts.google.com/.well-known/openid-configuration',
oidcClientId: '<OpenID Client ID>',
oidcClientSecret: '<OpenID Client Secret>',
clientID: '<clientID of the OIDC SSO Connection>',
clientSecret: '<clientSecret of the OIDC SSO Connection>',
});

Get SAML/OIDC Connections

Get the details of an existing SAML or OIDC Single Sign-On connection.

// Using tenant and product
await connection.getConnections({
tenant: 'boxyhq',
product: 'your-app',
});

// Using the client ID
await connection.getConnections({
clientID: '<clientID of the SSO Connection to be retrieved>.',
});

Delete SAML/OIDC Connection

Update a SAML or OIDC Single Sign-On connection.

// Using tenant and product
await connection.deleteConnections({
tenant: 'boxyhq',
product: 'your-app',
});

// Using client ID and client secret
await connection.deleteConnections({
clientID: '<clientID of the SSO Connection>',
clientSecret: '<clientSecret of the SSO Connection>',
});

Single Sign-On Authentication

Handle OAuth 2.0 (or OIDC) Authorization request

To initiate the flow, the application must trigger an OAuth 2.0 (or OIDC) redirect to the authorization endpoint of your app. You'll use the authorize method within the authorization handler.

authorize will resolve the SSO URL (redirect_url) based on the connection configured for the tenant/product. The app needs to redirect the user to this URL. Keep in mind that the SSO URL structure is different based on the type of SSO Connection. For a SAML Connection, this will contain the SAMLRequest whereas for an OIDC Connection the SSO URL will be the Authorization endpoint with the OIDC request params (scope, response_type, etc.) attached.

await oauth.authorize({
tenant: 'boxyhq',
product: 'your-app',
redirect_uri:
'<app redirect URI to which Jackson sents back the authorization code after authentication>',
state:
'<opaque value from the app which will be returned back from Jackson, this is need to prevent CSRF attacks>',
response_type: 'code',
code_challenge:
'<transformed value of code_verifier used to prevent interception of authorization_code in PKCE flow>',
code_challenge_method:
'<transformation method applied on code_verifier to generate code_challenge>',
scope:
'<can contain space separated values such as openid or even encoded tenant/product>',
nonce:
'<string value used to associate a client session with an ID Token in openid flow, and to mitigate replay attacks>',
idp_hint:
'<this will contain the clientID of the SSO connection that the user selects in the case of multiple ones configured for a tenant/product>',
prompt: '<pass "login" to force authentication at the SAML IdP>',
});

Handle IdP Response

The response is sent back to your app after authentication at IdP. After the handling of this response, the profile of the authenticated user is extracted and stored against a short-lived code that is then sent back to the app. To handle the response use the appropriate method as detailed below:

SAML Response

Handle the response from the SAML Identity Provider. After successful authentication, IdP sends back (via browser POST) the SAMLResponse and RelayState to the Assertion Consumer Service (ACS) URL (samlPath) of the app. You'll use the samlResponse method within your ACS endpoint. This will parse and validate the SAML Response after which the user profile is extracted.

await oauth.samlResponse({
SAMLResponse: '<SAML Response from the SAML IdP>',
RelayState: '<Relaystate from the original SAML request to the IdP>',
});

OIDC Response

Handle the response from the OIDC Identity Provider. After successful authentication, IdP sends back (via browser redirect) the code and state to the redirect URL (oidcPath) that handles the OIDC response. You'll use the oidcAuthzResponse method within your oidcPath handler. This will exchange the code for tokenSet (id_token and access_token) from the OIDC Provider. The "userinfo" endpoint of the OIDC Provider also gets invoked. Both the id_token claims and userinfo response are used to form the user profile.

await oauth.oidcAuthzResponse({
code: '<code received from OIDC IdP after authentication>',
state: '<state from the original OIDC request to the IdP>',
});

Request Access Token

Requests an access_token by passing the authorization code from the previous step along with other authentication details.

const tenant = 'boxyhq';
const product = 'your-app';

await oauth.token({
code: '<Authorization code received from Jackson at redirect_url after login at IdP>',
redirect_uri:
'<redirect_uri used in original authorization request to Jackson>',
client_id: `tenant=${tenant}&product=${product}`,
client_secret: 'dummy',
grant_type: 'authorization_code',
});

Fetch User Profile

Once the access_token has been fetched, you can use it to retrieve the user profile from the Identity Provider.

const accessToken = '<Access token from code exchange step above>';

await oauth.userInfo(accessToken);