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 providerYour 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.
.xml
file to base64#
2) Convert the 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>MIIC4jCCAcoCCQC33wnybT5QZDANBgkqhkiG9w0BAQsFADAyMQswCQYDVQQGEwJVSzEPMA0GA1UECgwGQm94eUhRMRIwEAYDVQQDDAlNb2NrIFNBTUwwIBcNMjIwMjI4MjE0NjM4WhgPMzAyMTA3MDEyMTQ2MzhaMDIxCzAJBgNVBAYTAlVLMQ8wDQYDVQQKDAZCb3h5SFExEjAQBgNVBAMMCU1vY2sgU0FNTDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALGfYettMsct1T6tVUwTudNJH5Pnb9GGnkXi9Zw/e6x45DD0RuRONbFlJ2T4RjAE/uG+AjXxXQ8o2SZfb9+GgmCHuTJFNgHoZ1nFVXCmb/Hg8Hpd4vOAGXndixaReOiq3EH5XvpMjMkJ3+8+9VYMzMZOjkgQtAqO36eAFFfNKX7dTj3VpwLkvz6/KFCq8OAwY+AUi4eZm5J57D31GzjHwfjH9WTeX0MyndmnNB1qV75qQR3b2/W5sGHRv+9AarggJkF+ptUkXoLtVA51wcfYm6hILptpde5FQC8RWY1YrswBWAEZNfyrR4JeSweElNHg4NVOs4TwGjOPwWGqzTfgTlECAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAAYRlYflSXAWoZpFfwNiCQVE5d9zZ0DPzNdWhAybXcTyMf0z5mDf6FWBW5Gyoi9u3EMEDnzLcJNkwJAAc39Apa4I2/tml+Jy29dk8bTyX6m93ngmCgdLh5Za4khuU3AM3L63g7VexCuO7kwkjh/+LqdcIXsVGO6XDfu2QOs1Xpe9zIzLpwm/RNYeXUjbSj5ce/jekpAw7qyVVL4xOyh8AtUW1ek3wIw1MJvEgEPt0d16oshWJpoS1OT8Lr/22SvYEo3EmSGdTVGgk3x3s+A0qWAqTcyjr7Q4s/GKYRFfomGwz0TZ4Iw1ZN99Mm0eo2USlSRTVl7QHRTuiuSThHpLKQQ==</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 serviceYou 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 JacksonTake 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- ReactJS
- Angular
- Vue
- Plain JavaScript
- React Native
Note
supertokens-web-js
SDK which exposes several helper functions that query the APIs exposed by the SuperTokens backend SDK.You can refer to this example app as a reference for using the
supertokens-web-js
SDK.Note
supertokens-react-native
SDK. The SDK provides session management features.To add login functionality, you need to build your own UI and call the APIs exposed by the backend SDKs. You can find the API spec here
import React from "react";import SuperTokens from "supertokens-auth-react";import ThirdParty from "supertokens-auth-react/recipe/thirdparty";SuperTokens.init({ appInfo: { apiDomain: "...", appName: "...", websiteDomain: "..." }, recipeList: [ ThirdParty.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- NodeJS
- GoLang
- Python
import axios from "axios";import SuperTokens from "supertokens-node";import Session from "supertokens-node/recipe/session";import ThirdParty from "supertokens-node/recipe/thirdparty";
SuperTokens.init({ appInfo: { apiDomain: "...", appName: "...", websiteDomain: "..." }, supertokens: { connectionURI: "...", }, recipeList: [ ThirdParty.init({ signInAndUpFeature: { 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({}) ]});
note
Please see the NodeJS tab and make similar changes to the Go integration. You can see how to integrate a custom OAuth provider in the Go SDK using this guide.
note
Please see the NodeJS tab and make similar changes to the Python integration. You can see how to integrate a custom OAuth provider in the Python SDK using this guide.
#
7) Supporting multiple tenantsThe 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.