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

This is a paid feature. Email us to get a license key to start a SuperTokens subscription.

If you want to try this feature without contacting us, you can sign up for our managed service, and use this feature in our development environment for free.

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).

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 should see buttons for social login. We want to replace these with the tenant's provider options.

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).

For this, we must first disable the default sign in UI and create our own route on which we will built our component. Start by setting the disableDefaultUI config in the supertokens.init function call as shown below and removing the list of providers you may have added earlier:

import React from 'react';

import SuperTokens, { SuperTokensWrapper } from "supertokens-auth-react";
import ThirdPartyPasswordless from "supertokens-auth-react/recipe/thirdpartypasswordless";
import Session from "supertokens-auth-react/recipe/session";

SuperTokens.init({
appInfo: {
appName: "...",
apiDomain: "...",
websiteDomain: "...",
apiBasePath: "...",
websiteBasePath: "..."
},
recipeList: [
ThirdPartyPasswordless.init({
contactMethod: "EMAIL", // this will work for any contactMethod
signInUpFeature: {
disableDefaultUI: true
}
}),
Session.init()
]
});

Now if you visit the login page, you will no longer see the login UI. We will now create our own login route on which we will display the following component:

import React from 'react';
import { SignInAndUp } from "supertokens-auth-react/recipe/thirdpartypasswordless/prebuiltui";

function TenantLoginPage() {

const [tenantId, setTenantId] = React.useState("");
const [tenantIdSubmitted, setTenantIdSubmitted] = React.useState(false);

React.useEffect(() => {
// for simplicity sakes, we want to ask the user their
// tenantId each time they reload this page. But you could skip
// this and show a UI to the user in which you display the existing tenantId (if present)
// and ask them to confirm it, or enter a new one
localStorage.removeItem("tenantId");
}, [])

if (!tenantIdSubmitted) {
// render UI to ask the user for their tenant ID
return (
<div>
<div>Enter your organisation's name:</div><br />
<input
type="text"
value={tenantId}
onChange={(e) => setTenantId(e.target.value)}
/>
<br /><br />
<button onSubmit={() => {

// this value will be read by SuperTokens as shown in the next steps.
localStorage.setItem("tenantId", tenantId)
setTenantIdSubmitted(true);
}}>Next</button>
</div>
)
} else {
// we now have their tenant ID, so we should render the sign in up component
// which will display the login UI configured for the user's tenant ID.
return <SignInAndUp />
}
}
  • 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 providers based on the saved tenantId.
  • The component above clears the tenantId from localstorage whenever this page is (re)loaded, but you can change the behaviour of that to show the existing saved tenantId and ask the user to confirm it or change it.
  • Finally, after saving the tenantId in localstorage, we make the component render the SignInAndUp UI component from SuperTokens lib which will render the UI we had disabled previously, but with the right set of providers for this tenant.

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 redirect the user to a specific route which further redirects them to their sub domain.

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", // this will work for any contactMethod
getRedirectionURL: async (context) => {
if (context.action === "SUCCESS") {
return "/redirect";
}
return undefined;
}
}),
]
});

Then on the /redirect page, you can redirect the user to their sub domain which is saved in their session:

import Session from "supertokens-web-js/recipe/session";
import Multitenancy from "supertokens-web-js/recipe/multitenancy";

async function redirectToSubDomain() {
if (await Session.doesSessionExist()) {
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
}
} else {
window.location.href = "/auth";
}
}
  • 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 { SessionAuth, getClaimValue } from 'supertokens-auth-react/recipe/session';
import { AllowedDomainsClaim } from 'supertokens-auth-react/recipe/multitenancy';

const ProtectedRoute = (props: React.PropsWithChildren<any>) => {
return (
<SessionAuth
overrideGlobalClaimValidators={(globalValidators) =>
[...globalValidators,
{
...AllowedDomainsClaim.validators.hasAccessToCurrentDomain(),
onFailureRedirection: async () => {
let claimValue: string[] | undefined = await getClaimValue({
claim: AllowedDomainsClaim
});
return "https://" + claimValue![0];
}
}
]
}
>
{props.children}
</SessionAuth>
);
}

Above we are creating a generic component called ProtectedRoute which enforces that its child components can only be rendered if the user is on the right sub domain based on the Multi tenancy claim. In case the claim validation fails, we redirect the user to their right sub domain based on that's configured by you on the backend (via the AllowedDomainsClaim session claim).