Skip to main content

Integration Steps

We will be following the code in our example app with SuperTokens + SAML Jackson, for React and NodeJS express apps.

  • If you use a different backend SDK than NodeJS, you can follow similar steps as mentioned in this guide, but also see the guide for integrating with a custom OAuth provider (for the SDK that you use).
  • If you are not using our supertokens-auth-react SDK, then please see this blog post for how to manually make API calls to the backend to complete the OAuth flow.
important

This guide assumes that you have completed the frontend and backend quick setup guide that is mentioned in the previous sections and have connected your app to a SuperTokens core (either self hosted or managed service).

1) Generate the XML metadata file from your SAML provider#

Your SAML provider will allow you to download a .xml file that you can upload to SAML Jackson. During this process, you will need to provide it:

  • the SSO URL and;
  • the Entity ID.

You can learn more about these in the SAML Jackson docs.

In our example app, we use mocksaml.com which is a free SAML provider that we can use for testing. When you navigate to the site, you will see a "Download metadata" button which you should click on to get the .xml file.

2) Convert the .xml file to base64#

You can use an online base64 encoder to do this. First copy the contents of the .xml file, and then put it in the encoder tool. The output string will be the base64 version of the .xml file.

For example, with an input .xml file (obtained from mocksaml.com):

<?xml version="1.0" encoding="UTF-8" standalone="no"?><EntityDescriptor xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata" entityID="https://saml.example.com/entityid" validUntil="2026-06-22T18:39:53.000Z"><IDPSSODescriptor WantAuthnRequestsSigned="false" protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol"><KeyDescriptor use="signing"><KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#"><X509Data><X509Certificate>MIIC4jCCAcoCCQC33wnybT5QZDANBgkqhkiG9w0BAQsFADAyMQswCQYDVQQGEwJV
SzEPMA0GA1UECgwGQm94eUhRMRIwEAYDVQQDDAlNb2NrIFNBTUwwIBcNMjIwMjI4
MjE0NjM4WhgPMzAyMTA3MDEyMTQ2MzhaMDIxCzAJBgNVBAYTAlVLMQ8wDQYDVQQK
DAZCb3h5SFExEjAQBgNVBAMMCU1vY2sgU0FNTDCCASIwDQYJKoZIhvcNAQEBBQAD
ggEPADCCAQoCggEBALGfYettMsct1T6tVUwTudNJH5Pnb9GGnkXi9Zw/e6x45DD0
RuRONbFlJ2T4RjAE/uG+AjXxXQ8o2SZfb9+GgmCHuTJFNgHoZ1nFVXCmb/Hg8Hpd
4vOAGXndixaReOiq3EH5XvpMjMkJ3+8+9VYMzMZOjkgQtAqO36eAFFfNKX7dTj3V
pwLkvz6/KFCq8OAwY+AUi4eZm5J57D31GzjHwfjH9WTeX0MyndmnNB1qV75qQR3b
2/W5sGHRv+9AarggJkF+ptUkXoLtVA51wcfYm6hILptpde5FQC8RWY1YrswBWAEZ
NfyrR4JeSweElNHg4NVOs4TwGjOPwWGqzTfgTlECAwEAATANBgkqhkiG9w0BAQsF
AAOCAQEAAYRlYflSXAWoZpFfwNiCQVE5d9zZ0DPzNdWhAybXcTyMf0z5mDf6FWBW
5Gyoi9u3EMEDnzLcJNkwJAAc39Apa4I2/tml+Jy29dk8bTyX6m93ngmCgdLh5Za4
khuU3AM3L63g7VexCuO7kwkjh/+LqdcIXsVGO6XDfu2QOs1Xpe9zIzLpwm/RNYeX
UjbSj5ce/jekpAw7qyVVL4xOyh8AtUW1ek3wIw1MJvEgEPt0d16oshWJpoS1OT8L
r/22SvYEo3EmSGdTVGgk3x3s+A0qWAqTcyjr7Q4s/GKYRFfomGwz0TZ4Iw1ZN99M
m0eo2USlSRTVl7QHRTuiuSThHpLKQQ==
</X509Certificate></X509Data></KeyInfo></KeyDescriptor><NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress</NameIDFormat><SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="https://mocksaml.com/api/saml/sso"/><SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://mocksaml.com/api/saml/sso"/></IDPSSODescriptor></EntityDescriptor>

The base64 output is:

PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+PEVudGl0eURlc2NyaXB0b3IgeG1sbnM6bWQ9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDptZXRhZGF0YSIgZW50aXR5SUQ9Imh0dHBzOi8vc2FtbC5leGFtcGxlLmNvbS9lbnRpdHlpZCIgdmFsaWRVbnRpbD0iMjAyNi0wNi0yMlQxODozOTo1My4wMDBaIj48SURQU1NPRGVzY3JpcHRvciBXYW50QXV0aG5SZXF1ZXN0c1NpZ25lZD0iZmFsc2UiIHByb3RvY29sU3VwcG9ydEVudW1lcmF0aW9uPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6cHJvdG9jb2wiPjxLZXlEZXNjcmlwdG9yIHVzZT0ic2lnbmluZyI+PEtleUluZm8geG1sbnM6ZHM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyMiPjxYNTA5RGF0YT48WDUwOUNlcnRpZmljYXRlPk1JSUM0akNDQWNvQ0NRQzMzd255YlQ1UVpEQU5CZ2txaGtpRzl3MEJBUXNGQURBeU1Rc3dDUVlEVlFRR0V3SlYKU3pFUE1BMEdBMVVFQ2d3R1FtOTRlVWhSTVJJd0VBWURWUVFEREFsTmIyTnJJRk5CVFV3d0lCY05Nakl3TWpJNApNakUwTmpNNFdoZ1BNekF5TVRBM01ERXlNVFEyTXpoYU1ESXhDekFKQmdOVkJBWVRBbFZMTVE4d0RRWURWUVFLCkRBWkNiM2g1U0ZFeEVqQVFCZ05WQkFNTUNVMXZZMnNnVTBGTlREQ0NBU0l3RFFZSktvWklodmNOQVFFQkJRQUQKZ2dFUEFEQ0NBUW9DZ2dFQkFMR2ZZZXR0TXNjdDFUNnRWVXdUdWROSkg1UG5iOUdHbmtYaTlady9lNng0NUREMApSdVJPTmJGbEoyVDRSakFFL3VHK0FqWHhYUThvMlNaZmI5K0dnbUNIdVRKRk5nSG9aMW5GVlhDbWIvSGc4SHBkCjR2T0FHWG5kaXhhUmVPaXEzRUg1WHZwTWpNa0ozKzgrOVZZTXpNWk9qa2dRdEFxTzM2ZUFGRmZOS1g3ZFRqM1YKcHdMa3Z6Ni9LRkNxOE9Bd1krQVVpNGVabTVKNTdEMzFHempId2ZqSDlXVGVYME15bmRtbk5CMXFWNzVxUVIzYgoyL1c1c0dIUnYrOUFhcmdnSmtGK3B0VWtYb0x0VkE1MXdjZlltNmhJTHB0cGRlNUZRQzhSV1kxWXJzd0JXQUVaCk5meXJSNEplU3dlRWxOSGc0TlZPczRUd0dqT1B3V0dxelRmZ1RsRUNBd0VBQVRBTkJna3Foa2lHOXcwQkFRc0YKQUFPQ0FRRUFBWVJsWWZsU1hBV29acEZmd05pQ1FWRTVkOXpaMERQek5kV2hBeWJYY1R5TWYwejVtRGY2RldCVwo1R3lvaTl1M0VNRURuekxjSk5rd0pBQWMzOUFwYTRJMi90bWwrSnkyOWRrOGJUeVg2bTkzbmdtQ2dkTGg1WmE0CmtodVUzQU0zTDYzZzdWZXhDdU83a3dramgvK0xxZGNJWHNWR082WERmdTJRT3MxWHBlOXpJekxwd20vUk5ZZVgKVWpiU2o1Y2UvamVrcEF3N3F5VlZMNHhPeWg4QXRVVzFlazN3SXcxTUp2RWdFUHQwZDE2b3NoV0pwb1MxT1Q4TApyLzIyU3ZZRW8zRW1TR2RUVkdnazN4M3MrQTBxV0FxVGN5anI3UTRzL0dLWVJGZm9tR3d6MFRaNEl3MVpOOTlNCm0wZW8yVVNsU1JUVmw3UUhSVHVpdVNUaEhwTEtRUT09CjwvWDUwOUNlcnRpZmljYXRlPjwvWDUwOURhdGE+PC9LZXlJbmZvPjwvS2V5RGVzY3JpcHRvcj48TmFtZUlERm9ybWF0PnVybjpvYXNpczpuYW1lczp0YzpTQU1MOjEuMTpuYW1laWQtZm9ybWF0OmVtYWlsQWRkcmVzczwvTmFtZUlERm9ybWF0PjxTaW5nbGVTaWduT25TZXJ2aWNlIEJpbmRpbmc9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpiaW5kaW5nczpIVFRQLVJlZGlyZWN0IiBMb2NhdGlvbj0iaHR0cHM6Ly9tb2Nrc2FtbC5jb20vYXBpL3NhbWwvc3NvIi8+PFNpbmdsZVNpZ25PblNlcnZpY2UgQmluZGluZz0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmJpbmRpbmdzOkhUVFAtUE9TVCIgTG9jYXRpb249Imh0dHBzOi8vbW9ja3NhbWwuY29tL2FwaS9zYW1sL3NzbyIvPjwvSURQU1NPRGVzY3JpcHRvcj48L0VudGl0eURlc2NyaXB0b3I+

3) Start the SAML Jackson service#

You can run SAML Jackson with or without Docker.

docker run \
-p 5225:5225 \
-e JACKSON_API_KEYS="secret" \
-e DB_ENGINE="sql" \
-e DB_TYPE="postgres" \
-e DB_URL="postgres://postgres:postgres@postgres:5432/postgres" \
-d boxyhq/jackson

Instead, if you are following the example guide, run:

yarn dev

in the root directory of the project and this will start the SAML jackson server on http://localhost:5225 (along with a SuperTokens core + postgresql server)

4) Upload the base64 xml string to SAML Jackson#

Take the string generated in step (2) and run:

curl --location --request POST 'http://localhost:5225/api/v1/saml/config' \
--header 'Authorization: Api-Key secret' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'encodedRawMetadata=<STRING_FROM_STEP_2>' \
--data-urlencode 'defaultRedirectUrl=<REDIRECT_URI>' \
--data-urlencode 'redirectUrl=["<REDIRECT_URI>"]' \
--data-urlencode 'tenant=<TENANT_ID>' \
--data-urlencode 'product=<PRODUCT_NAME>' \
--data-urlencode 'name=demo-config' \
--data-urlencode 'description=Demo SAML config'

You can learn more about the config values in the SAML Jackson docs.

For our example app, you can see this command here. This is provided in a helper script called addTenant.sh. You can run it like:

./addTenant.sh <TENANT_ID>

# example
./addTenant.sh app1.com
./addTenant.sh app2.com

In this guide, we will assume that there is only one tenant configured. If you want to configure multiple tenants, that will be covered in the next page.

note

The output of this command will provide you the client_id and client_secret for this tenant. You will need to use these values in the OAuth flow when configuring SuperTokens to talk to SAML Jackson.

5) Modify your frontend app to add a Login with SAML button#

import React from "react";
import SuperTokens from "supertokens-auth-react";
import ThirdPartyEmailPassword from "supertokens-auth-react/recipe/thirdpartyemailpassword";
SuperTokens.init({
appInfo: {
apiDomain: "...",
appName: "...",
websiteDomain: "..."
},
recipeList: [
ThirdPartyEmailPassword.init({
signInAndUpFeature: {
providers: [
{
id: "saml-jackson",
name: "SAML",

// optional
// you do not need to add a click handler to this as
// we add it for you automatically.
buttonComponent: <div style={{
cursor: "pointer",
border: "1",
paddingTop: "5px",
paddingBottom: "5px",
borderRadius: "5px",
borderStyle: "solid"
}}>Login with SAML</div>
}
],
// ...
},
// ...
}),
// ...
]
});

The above will add a "Login with SAML" button to your list of third party providers. Notice that the id used is saml-jackson. This same id will be used on the backend too, and the redirect URL will be {websiteDomain}/{websiteBasePath}/auth/callback/saml-jackson

6) Add a custom OAuth provider on the backend server code#

import axios from "axios";
import SuperTokens from "supertokens-node";
import Session from "supertokens-node/recipe/session";
import ThirdPartyEmailPassword from "supertokens-node/recipe/thirdpartyemailpassword";

SuperTokens.init({
appInfo: {
apiDomain: "...",
appName: "...",
websiteDomain: "..."
},
supertokens: {
connectionURI: "...",
},
recipeList: [
ThirdPartyEmailPassword.init({
providers: [
{
id: "saml-jackson",
get: (redirectURI, authCodeFromRequest, userContext) => {
let client_id = "TODO: From output of step 4";
let client_secret = "TODO: From output of step 4";
return {
accessTokenAPI: {
url: `http://localhost:5225/api/oauth/token`,
params: {
client_id,
client_secret,
grant_type: "authorization_code",
redirect_uri: redirectURI || "",
code: authCodeFromRequest || "",
}
},
authorisationRedirect: {
url: `http://localhost:5225/api/oauth/authorize`,
params: {
client_id
}
},
getClientId: () => {
return client_id;
},
getProfileInfo: async (accessTokenAPIResponse) => {
const profile = await axios({
method: 'get',
url: `http://localhost:5225/api/oauth/userinfo`,
headers: {
Authorization: `Bearer ${accessTokenAPIResponse.access_token}`,
},
});
return {
id: profile.data.id,
email: {
id: profile.data.email,
isVerified: true
}
};
}
}
}
}
]
}),
Session.init({})
]
});

7) Supporting multiple tenants#

The above shows you how to get SAML Jackson + SuperTokens to work with a single tenant. If you would like to support multiple tenants, please see the next page.