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 authentication method that is displayed on the login page adjusts 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
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 are required by your application. 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 so that the user is first asked to enter their tenant ID and then the login UI appears 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 is built 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 is considered as their tenant ID.
When the user submits that form, the value is stored 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, the tenantId
can be obtained from local storage.
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...
]
});
The usesDynamicLoginMethods
is set to true
which tells 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 can then be accessed 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 is redirected to a sub domain, the session recipe needs to be updated.
It should allow sharing of sessions across sub domains.
This can be done 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.
7. Limiting user access to their sub domain. Optional
Session claim validators are used on the frontend to restrict sub domain access.
Before proceeding, make sure that the GetAllowedDomainsForTenantId
function mentioned above is defined.
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.
This can be achieved 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, the hasAccessToCurrentDomain
claim validator is added to the global validators. This means, that whenever a route is protected, it checks if hasAccessToCurrentDomain
has passed, and if not, SuperTokens redirects the user to their right sub domain (via the values set in the AllowedDomainsClaim
session claim).