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.
- Request
- Response
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',
});
{
"defaultRedirectUrl": "https://your-app.com/sso/callback",
"redirectUrl": ["https://your-app.com/*"],
"tenant": "boxyhq",
"product": "your-app",
"clientID": "f7c909a5c72a5535847acf32558b2429a5172dd6",
"clientSecret": "cc6ba07bc42c2f449c9b0a3cc41c256dea08f705e1b44fdc",
"forceAuthn": false,
"idpMetadata": {
"sso": {
"postUrl": "https://mocksaml.com/api/saml/sso",
"redirectUrl": "https://mocksaml.com/api/saml/sso"
},
"slo": {},
"entityID": "https://saml.example.com/entityid",
"thumbprint": "d797f3829882233d3f01e49643f6a1195f242c94",
"validTo": "Jul 1 21:46:38 3021 GMT",
"loginType": "idp",
"provider": "saml.example.com"
},
"certs": {
"publicKey": "-----BEGIN CERTIFICATE----- ... -----END CERTIFICATE-----\r\n",
"privateKey": "-----BEGIN RSA PRIVATE KEY----- ... -----END RSA PRIVATE KEY-----\r\n"
}
}
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.
- Request
- Response
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>',
});
{
"defaultRedirectUrl": "https://your-app.com/sso/callback",
"redirectUrl": ["https://your-app.com/*"],
"tenant": "boxyhq",
"product": "your-app",
"clientID": "749f95c4bd02b4adb6c0633249e70d5ad45b75e2",
"clientSecret": "2d730ac71c74e7d49dccf362c9a61005b6246cc65d6d0fa4",
"oidcProvider": {
"discoveryUrl": "https://accounts.google.com/.well-known/openid-configuration",
"clientId": "<OpenID Client ID>",
"clientSecret": "<OpenID Client Secret>",
"provider": "accounts.google.com"
}
}
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.
- Request
- Response
// 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>.',
});
[
{
"defaultRedirectUrl": "https://your-app.com/sso/callback",
"redirectUrl": ["https://your-app.com/*"],
"tenant": "boxyhq",
"product": "your-app",
"clientID": "...",
"clientSecret": "...",
"forceAuthn": false,
"idpMetadata": {
"sso": {
"postUrl": "https://mocksaml.com/api/saml/sso",
"redirectUrl": "https://mocksaml.com/api/saml/sso"
},
"slo": {},
"entityID": "https://saml.example.com/entityid",
"thumbprint": "d797f3829882233d3f01e49643f6a1195f242c94",
"validTo": "Jul 1 21:46:38 3021 GMT",
"loginType": "idp",
"provider": "saml.example.com"
},
"certs": {
"publicKey": "-----BEGIN CERTIFICATE----- ... -----END CERTIFICATE-----\r\n",
"privateKey": "-----BEGIN RSA PRIVATE KEY----- ... -----END RSA PRIVATE KEY-----\r\n"
}
}
]
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.
- Request
- Response
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>',
});
{
"redirect_url": "https://mocksaml.com/api/saml/sso?RelayState=boxyhq_jackson_...&SAMLRequest=nVbZkqs4En33V1T4...",
"authorize_form": ""
}
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.
- Request
- Response
await oauth.samlResponse({
SAMLResponse: '<SAML Response from the SAML IdP>',
RelayState: '<Relaystate from the original SAML request to the IdP>',
});
{
"redirect_url": "https://your-app.com/sso/callback?code=5db7257fde94e062f6243572e31818d6e64c3097&state=c38ee339-6b82-43d3-838f-4036820acce9"
}
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.
- Request
- Response
await oauth.oidcAuthzResponse({
code: '<code received from OIDC IdP after authentication>',
state: '<state from the original OIDC request to the IdP>',
});
{
"redirect_url": "https://your-app.com/sso/callback?code=5db7257fde94e062f6243572e31818d6e64c3097&state=c38ee339-6b82-43d3-838f-4036820acce9"
}
Request Access Token
Requests an access_token
by passing the authorization code
from the previous step along with other authentication details.
- Request
- Response
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',
});
{
"access_token": "6b81f03b60c34e46e740d96c7e6242923736a2d1",
"token_type": "bearer",
"expires_in": 300
}
Fetch User Profile
Once the access_token
has been fetched, you can use it to retrieve the user profile from the Identity Provider.
- Request
- Response
const accessToken = '<Access token from code exchange step above>';
await oauth.userInfo(accessToken);
{
"raw": {
"id": "1dda9fb491dc01bd24d2423ba2f22ae561f56ddf2376b29a11c80281d21201f9",
"email": "[email protected]",
"firstName": "Samuel",
"lastName": "Jackson",
"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier": "[email protected]"
},
"id": "1dda9fb491dc01bd24d2423ba2f22ae561f56ddf2376b29a11c80281d21201f9",
"email": "[email protected]",
"firstName": "Samuel",
"lastName": "Jackson",
"requested": {
"client_id": "f7c909a5c72a5535847acf32558b2429a5172dd6",
"state": "c38ee339-6b82-43d3-838f-4036820acce9",
"redirect_uri": "https://your-app.com/sso/callback",
"tenant": "boxyhq",
"product": "your-app",
"scope": []
}
}