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>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 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 ThirdPartyPasswordless from "supertokens-auth-react/recipe/thirdpartypasswordless";SuperTokens.init({    appInfo: {        apiDomain: "...",        appName: "...",        websiteDomain: "..."    },    recipeList: [        ThirdPartyPasswordless.init({            contactMethod: "EMAIL",            signInUpFeature: {                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 ThirdPartyPasswordless from "supertokens-node/recipe/thirdpartypasswordless";
SuperTokens.init({    appInfo: {        apiDomain: "...",        appName: "...",        websiteDomain: "..."    },    supertokens: {        connectionURI: "...",    },    recipeList: [        ThirdPartyPasswordless.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.