Skip to main content
Which UI do you use?
Custom UI
Pre built UI
Paid Feature

This is a paid feature.

For self hosted users, Sign up to get a license key and follow the instructions sent to you by email. Creation of tenants is free on the dev license key.

This feature is already enabled for managed service users. Creation of additional tenant is free on the provided development environment.

Example 1: Tenants use a common domain to login

In this UX flow, all tenants login using the same page (like https://example.com/auth) and are redirected to their sub domain after login. The login method that's shown on the login page depend on the tenant's tenantId configuration.

The process of getting the tenantId from the user is left up to you, but a common method is to ask the user to enter their organisation name (which is equal to the tenantId that you configure in SuperTokens).

important

An example app for this setup with our pre built UI can be found on our github example dir. The app is setup to have three tenants:

  • tenant1: Login with emailpassword + Google sign in
  • tenant2: Login with emailPassword
  • tenant3: Login with passwordless + Github sign in

You can also generate a demo app using the following command:

npx create-supertokens-app@latest --recipe=multitenancy

Step 1: Creating a new tenant#

Whenever you want to onboard a new customer, you should create and configure a tenantId for them in the SuperTokens core.

Step 2: Ask for the tenant ID on the login page#

If you have followed the pre built UI setup, when you visit the login screen, you immediately see the login screen. We want to change the flow so that the user is first asked to enter their tenant ID and then the login UI is shown based on the tenant ID.

In order to do that, we must first get the tenant ID from the user. This can be done by building a UI that will ask them to enter their tenantId or Organisation name (which can be used as the tenant ID). We build this UI in a component called AuthPage (as shown below):

Do you use react-router-dom?
YesNo
  • We render a simple UI which asks the user for their organisation's name. Their input will be treated as their tenant ID.
  • Once the user has submitted that form, we will store their input in localstorage. This value will be read later on (as shown below) by SuperTokens to render the right login method based on the saved tenantId.
  • In case the tenantID exists in localstorage, we render the SuperTokens UI as usual.
  • We render the tenant form if the user has not yet provided us with the information about which tenant they belong to. In all our flows, we get the tenantId threw ways via:
    • If the user had previously etnered the tenantId and it's stored in localstorage
    • If there already exists a session - in which case we get the tenantId from the session in the pre built components.
    • If there is a query param called tenantId - in which case we get the tenantId from the query param in the pre built components. This query param will be there in email verification, password reset, or magic links.
  • In the code above, we render a box in which the user can enter their tenant ID / org name, but you can also make it a drop down or any other UI that you want - the key is that their input should be saveed in localstorage so that it can be read later
important

We want to render the AuthPage component to show on /auth/* paths of the website.

The AuthPage will replace the call to getSuperTokensRoutesForReactRouterDom or getRoutingComponent that you may have added to your app from the quick setup section.

Finally, we need to clear the saved tenantId from localstorage in the following events:

  • When the user signs out: Since the user can get to choose the tenant ID again when they sign in.
  • When the session has been revoked / expired and the frontend detects this: This will redirect the user to the login screen, where they can choose the tenant ID again.
  • When the user signs in: Since now the user's session will be the source of their tenant ID.
import Session from "supertokens-auth-react/recipe/session";

Session.init({
// other configs..
onHandleEvent: (event) => {
if (["SIGN_OUT", "UNAUTHORISED", "SESSION_CREATED"].includes(event.action)) {
window.localStorage.removeItem("tenantId");
}
},
})

Step 3: Tell SuperTokens about the saved tenantId from the previous step#

Initialise the multi tenancy recipe with the following callback which reads from the browser's localstorage to get the previously saved tenantId:

import React from 'react';

import SuperTokens, { SuperTokensWrapper } from "supertokens-auth-react";
import Multitenancy from "supertokens-auth-react/recipe/multitenancy";

SuperTokens.init({
appInfo: {
appName: "...",
apiDomain: "...",
websiteDomain: "...",
apiBasePath: "...",
websiteBasePath: "..."
},
usesDynamicLoginMethods: true,
recipeList: [
Multitenancy.init({
override: {
functions: (oI) => {
return {
...oI,
getTenantId: (input) => {
let tid = localStorage.getItem("tenantId");
return tid === null ? undefined : tid;
}
}
}
}
})
// other recipes...
]
});
important

We also set the usesDynamicLoginMethods to true which tells SuperTokens that the login methods are dynamic (based on the tenantId). This means that on page load (of the login page), SuperTokens will first fetch the configured login methods for the tenantId and display the login UI based on the result of the API call.

Step 4: (Optional) Tell SuperTokens about tenant's sub domains#

You may have a flow in which each tenant has access to specific sub domains in your application. So after login, you would not only want to redirect those users to their sub domain, but you also want to restrict which sub domains they have access to.

SuperTokens makes it easy for you to do this. We start by telling SuperTokens which domain each tenantId has access to:

import SuperTokens from "supertokens-node";
import Multitenancy from "supertokens-node/recipe/multitenancy"

SuperTokens.init({
appInfo: {
apiDomain: "...",
appName: "...",
websiteDomain: "..."
},
recipeList: [
Multitenancy.init({
getAllowedDomainsForTenantId: async (tenantId, userContext) => {
// query your db to get the allowed domain for the input tenantId
// or you can make the tenantId equal to the sub domain itself
return [tenantId + ".myapp.com", "myapp.com", "www.myapp.com"]
}
}),
// other recipes...
]
})

The config above will tell SuperTokens to add the list of domains returned by you into the user's session claims once they login. This claim can then be read on the frontend and backend to restrict user's access to the right domain(s).

Step 5: (Optional) Redirect the user to their sub domain post sign in#

On the frontend side, post sign in, by default, our frontend SDK will redirect the user to the / route. You can change this to instead redirect them to their sub domain based on their tenantId.

import SuperTokens from "supertokens-auth-react";
import EmailPassword from "supertokens-auth-react/recipe/emailpassword";
import Session from "supertokens-auth-react/recipe/session";
import Multitenancy from "supertokens-auth-react/recipe/multitenancy";

SuperTokens.init({
appInfo: {
apiDomain: "...",
appName: "...",
websiteDomain: "...",
},
getRedirectionURL: async (context) => {
if (context.action === "SUCCESS" && context.newSessionCreated) {
let claimValue: string[] | undefined = await Session.getClaimValue({
claim: Multitenancy.AllowedDomainsClaim
});
if (claimValue !== undefined) {
window.location.href = "https://" + claimValue[0];
} else {
// there was no configured allowed domain for this user. Throw an error cause of
// misconfig or redirect to a default sub domain
}
}
return undefined;
},
recipeList: [
EmailPassword.init({

}),
]
});
  • The AllowedDomainsClaim claim is auto added to the session by our backend SDK if you provide the GetAllowedDomainsForTenantId config from the previous step.
  • This claim contains a list of domains that are allowed for this user, based on their tenant ID.

Step 6: (Optional) Sharing sessions across sub domains#

Since the user logged into your main website domain (https://example.com/auth), and are being redirected to their sub domain, we need to configure the session recipe to allow sharing of sessions across sub domains. This can be achieved by setting the sessionTokenFrontendDomain value in the Session recipe.

If the sub domains assigned to your tenants have their own backend, on a separate sub domain (one per tenant), then you can also enable sharing of sessions across API domains.

Step 7: (Optional) Limiting the user's access to their sub domain.#

We will be using session claim validators on the frontend to restrict sub domain access. Before proceeding, make sure that you have defined the GetAllowedDomainsForTenantId function mentioned above. This will add the list of allowed domains into the user's access token payload.

On the frontend, we want to check if the tenant has access to the current sub domain. If not, we want to redirect them to the right sub domain. This can be done by using the hasAccessToCurrentDomain session validator from the multi tenancy recipe:

import React from "react";
import Session from 'supertokens-auth-react/recipe/session';
import { AllowedDomainsClaim } from 'supertokens-auth-react/recipe/multitenancy';

Session.init({
override: {
functions: (oI) => ({
...oI,
getGlobalClaimValidators: ({ claimValidatorsAddedByOtherRecipes }) => [
...claimValidatorsAddedByOtherRecipes,
{
...AllowedDomainsClaim.validators.hasAccessToCurrentDomain(),
onFailureRedirection: async () => {
let claimValue = await Session.getClaimValue({
claim: AllowedDomainsClaim,
});
return "https://" + claimValue![0];
},
},
],
}),
},
})

Above, in Session.init on the frontend, we add the hasAccessToCurrentDomain claim validator to the global validators. This means, that whenever we check protect a route, it will check if hasAccessToCurrentDomain has passed, and if not, SuperTokens will redirect to the user to their right sub domain (via the values set in the AllowedDomainsClaim session claim).