Common domain login
Authenticate users across different tenants through a common domain.
Overview
This guide shows you how to authenticate users through the same page, https://example.com/auth
, and then redirect to their sub domain after login.
The login page adjusts the authentication method based on the tenant's configuration.
You can figure out which is the tenant that you are working with through different methods.
A common way is to ask the user to enter their organisation name, which is equal to the tenantId
that you configure in SuperTokens.
You can find an example app for this setup with the pre-built UI on the GitHub example directory. The app is setup to have three tenants:
tenant1
: Login withemailpassword
+ Google sign intenant2
: Login withemailpassword
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
Before you start
This feature is only available to paid users.
The tutorial assumes that you already have a working application integrated with SuperTokens. If you have not, please check the Quickstart Guide.
You also need to create the tenants that your application requires. View the previous tutorial for more information on how to do this.
Steps
1. Ask for the tenant ID on the login page
What type of UI are you using?
App Info
Adjust these values based on the application that you are trying to configure. To learn more about what each field means check the references page.If you have followed the pre-built UI setup, when you visit the login screen, you see the login screen immediately. The flow needs to change to first ask the user to enter their tenant ID and then display the login UI based on the tenant ID.
To do that, first obtain the tenant ID from the user.
You can achieve this by building a UI that asks them to enter their tenantId
or Organisation name (which can serve as the tenant ID).
This UI builds in a component called AuthPage
.
You have to create tenants before you can complete this step.
Do you use react-router-dom?
import { useState } from "react";
import * as reactRouterDom from "react-router-dom";
import { Routes } from "react-router-dom";
import { getSuperTokensRoutesForReactRouterDom } from "supertokens-auth-react/ui";
import { EmailPasswordPreBuiltUI } from 'supertokens-auth-react/recipe/emailpassword/prebuiltui';
import { useSessionContext } from "supertokens-auth-react/recipe/session";
export const AuthPage = () => {
const location = reactRouterDom.useLocation();
const [inputTenantId, setInputTenantId] = useState("");
const tenantId = localStorage.getItem("tenantId") ?? undefined;
const session = useSessionContext();
if (session.loading) {
return null;
}
if (
tenantId !== undefined || // if we have a tenantId stored
session.doesSessionExist === true || // or an active session (it'll contain the tenantId)
new URLSearchParams(location.search).has("tenantId") // or we are on a link (e.g.: email verification) that contains the tenantId
) {
// Since this component (AuthPage) is rendered in the /auth route in the main Routes component,
// and we are rendering this in a sub route as shown below, the third arg to getSuperTokensRoutesForReactRouterDom
// tells SuperTokens to create Routes without /auth prefix to them, otherwise they would
// render on /auth path.
return (
<Routes>
{getSuperTokensRoutesForReactRouterDom(
reactRouterDom,
[EmailPasswordPreBuiltUI],
"/auth"
)}
</Routes>
);
} else {
return (
<form
onSubmit={() => {
// this value will be read by SuperTokens as shown in the next steps.
localStorage.setItem("tenantId", inputTenantId);
}}>
<h2>Enter your organisation's name:</h2>
<input type="text" value={inputTenantId} onChange={(e) => setInputTenantId(e.target.value)} />
<br />
<button type="submit">Next</button>
</form>
);
}
};
The example creates a simple UI renders which asks the user for their organisation's name.
Their input serves as their tenant ID.
When the user submits that form, the value stores in localstorage
.
The AuthPage
component should render to show on /auth/*
paths of the website.
The AuthPage
replaces the call to getSuperTokensRoutesForReactRouterDom
or getRoutingComponent
that you may have added to your app from the quick setup section.
2. Include the tenant ID in authentication flow
You need to tell SuperTokens how to resolve the tenant ID.
To do this, set the getTenantId
function in the Multitenancy
recipe.
In the current example, local storage provides the 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...
]
});
Set the usesDynamicLoginMethods
to true
to tell SuperTokens that the login methods are dynamic (based on the tenantId
). On page load (of the login page), SuperTokens first fetches the configured login methods for the tenantId
. It then displays the login UI based on the result of the API call.
3. Redirect users based on tenant subdomain Optional
If each tenant has access to specific sub domains in your application you need to redirect them after login.
3.1 Restrict subdomain access
Before you perform the actual redirect step, you should restrict which sub domains they have access to.
To do this configure the SDK to know 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 code sample tells SuperTokens to add the list of domains into the user's session claims once they login. This claim then becomes accessible on the frontend and backend to restrict user's access to the right domain(s).
3.2 Redirect the user to their sub domain after sign in
On the frontend side, post sign in, by default, the frontend SDK redirects the user to the /
route.
Can change this to instead redirect them to their sub domain based on their tenantId
.
import SuperTokens from "supertokens-auth-react";
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: [ /* Recipe init here... */ ]
});
- The
AllowedDomainsClaim
claim is auto added to the session by the backend SDK if you provide theGetAllowedDomainsForTenantId
configuration from the previous step. - This claim contains a list of domains that this user can access, based on their tenant ID.
6. Share sessions across sub domains Optional
If the user authenticates into your main website domain (https://example.com/auth
), and redirects to a sub domain, the session recipe needs updating.
It should allow sharing of sessions across sub domains.
You can do this 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), you can also enable sharing of sessions across API domains.
7. Limit user access to their sub domain. Optional
The frontend uses session claim validators to restrict sub domain access.
Before proceeding, make sure that you define the GetAllowedDomainsForTenantId
function mentioned above.
This adds the list of allowed domains into the user's access token payload.
On the frontend, it is necessary to check if the tenant has access to the current sub domain.
If not, they should redirect to the right sub domain.
You can achieve this 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, add the hasAccessToCurrentDomain
claim validator to the global validators. This means that whenever a route requires protection, it checks if hasAccessToCurrentDomain
has passed. If not, SuperTokens redirects the user to their right sub domain (via the values set in the AllowedDomainsClaim
session claim).