Implement subdomain login
Authenticate users across different tenants through different subdomains.
Overview
This guide shows you how to authenticate users through different subdomains. The authentication method displayed on each page varies based on the tenant configuration.
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.
Your application also needs you to create the tenants it requires. View the previous tutorial for more information on how to do this.
Steps
1. Change the CORS settings and websiteDomain
1.1 CORS setup
For browsers to make requests to the backend, configure backend CORS with the exact allowed origins.
For example, if the frontend uses https://customer1.example.com, allow that full origin. If tenants are dynamic,
validate the request’s full Origin value against an anchored pattern that permits only your intended HTTPS subdomains.
1.2 websiteDomain setup
Set the websiteDomain to window.location.origin in the frontend SDK initialization step.
On the backend, update websiteDomain to the main domain (example.com if your subdomains are sub.example.com).
Then override the sendEmail functions to change the domain of the link dynamically based on the tenant ID supplied to the sendEmail function.
See the Email Delivery section in the docs for how to override the sendEmail function.
2. Load login methods dynamically on the frontend based on the tenantId
Modify SuperTokens.init as follows:
- Set
usesDynamicLoginMethodstotrue. This tells the frontend SDK that the login page relies on the tenant ID and must fetch the tenant configuration from the backend before showing any login UI. - Initialize the
Multitenancyrecipe and provide thegetTenantIdconfiguration function.
import React from "react";
import SuperTokens, { SuperTokensWrapper } from "supertokens-auth-react";
import ThirdParty from "supertokens-auth-react/recipe/thirdparty";
import Session from "supertokens-auth-react/recipe/session";
import Multitenancy from "supertokens-auth-react/recipe/multitenancy";
SuperTokens.init({
appInfo: {
appName: "<YOUR_APP_NAME>",
apiDomain: "<YOUR_API_DOMAIN>",
websiteDomain: "<YOUR_WEBSITE_DOMAIN>",
apiBasePath: "/auth",
websiteBasePath: "/auth",
},
usesDynamicLoginMethods: true,
recipeList: [
// Other recipes..
Multitenancy.init({
override: {
functions: (oI) => {
return {
...oI,
getTenantId: async () => {
// We treat the subdomain as the tenant ID
return window.location.host.split(".")[0];
},
};
},
},
}),
],
});// this goes in the auth route config of your frontend app (once the pre-built UI script has been loaded)
supertokensUIInit({
appInfo: {
appName: "<YOUR_APP_NAME>",
apiDomain: "<YOUR_API_DOMAIN>",
websiteDomain: "<YOUR_WEBSITE_DOMAIN>",
apiBasePath: "/auth",
websiteBasePath: "/auth",
},
usesDynamicLoginMethods: true,
recipeList: [
// Other recipes...
supertokensUISession.init(),
supertokensUIMultitenancy.init({
override: {
functions: (oI) => {
return {
...oI,
getTenantId: async () => {
// We treat the subdomain as the tenant ID
return window.location.host.split(".")[0];
},
};
},
},
}),
],
});You can fetch the user’s login methods based on their tenant ID, which you can derive from the current subdomain, as shown below.
import Multitenancy from "supertokens-web-js/recipe/multitenancy";
async function fetchThirdPartyLoginProvidersForTenant(tenantId: string) {
const loginMethods = await Multitenancy.getLoginMethods({
tenantId,
});
if (loginMethods.firstFactors.includes("thirdparty")) {
const providers = loginMethods.thirdParty.providers;
if (providers.find((i) => i.id === "active-directory")) {
// render sign in with Active Directory button
} else {
// more checks for other providers
}
} else {
// thirdparty login is disabled for the tenant
}
}import Multitenancy from "supertokens-web-js/recipe/multitenancy";
async function fetchThirdPartyLoginProvidersForTenant(tenantId: string) {
const loginMethods = await Multitenancy.getLoginMethods({
tenantId,
});
if (loginMethods.firstFactors.includes("thirdparty")) {
const providers = loginMethods.thirdParty.providers;
if (providers.find((i) => i.id === "active-directory")) {
// render sign in with Active Directory button
} else {
// more checks for other providers
}
} else {
// thirdparty login is disabled for the tenant
}
}curl --location --request GET '<YOUR_API_DOMAIN>/auth/loginmethods'The response body from the API call has a status property in it:
status: "OK": Therecipesfield contains information about which login methods are active along with the list of third party providers configured for this tenant.status: "GENERAL_ERROR": This is only possible if you have overridden the backend API to send back a custom error message which should display on the frontend.
You also need to initialize the multitenancy recipe with the following callback. You can get the tenant ID from the subdomain as shown below.
After you have shown the login methods and the user tries to sign in, follow all the steps for mobile app login similar to the social login steps. When calling the sign in up API, also pass in the tenantId in the request path. An example of this appears below:
import SuperTokens from "supertokens-web-js";
import Multitenancy from "supertokens-web-js/recipe/multitenancy";
SuperTokens.init({
appInfo: {
appName: "...",
apiDomain: "...",
},
recipeList: [
Multitenancy.init({
override: {
functions: (oI) => {
return {
...oI,
getTenantId: async () => {
// We treat the subdomain as the tenant ID
return window.location.host.split(".")[0];
},
};
},
},
}),
// other recipes...
],
});supertokens.init({
appInfo: {
appName: "...",
apiDomain: "...",
},
recipeList: [
supertokensMultitenancy.init({
override: {
functions: (oI) => {
return {
...oI,
getTenantId: async () => {
// We treat the subdomain as the tenant ID
return window.location.host.split(".")[0];
},
};
},
},
}),
// other recipes...
],
});curl --location --request POST '<YOUR_API_DOMAIN>/auth/signinup' \
--header 'Content-Type: application/json' \
--data-raw '{
"thirdPartyId": "...",
"clientType": "...",
"oAuthTokens": {
"access_token": "...",
"id_token": "..."
},
}'3. Restrict session use by subdomain
Restrict the subdomains on which a tenant’s sessions can be used. To do this, configure the SDK with the domains for each tenant ID.
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 subdomain itself
return [tenantId + ".myapp.com", "myapp.com", "www.myapp.com"];
},
}),
// other recipes...
],
});import (
"github.com/supertokens/supertokens-golang/recipe/multitenancy"
"github.com/supertokens/supertokens-golang/recipe/multitenancy/multitenancymodels"
"github.com/supertokens/supertokens-golang/supertokens"
)
func main() {
supertokens.Init(supertokens.TypeInput{
RecipeList: []supertokens.Recipe{
multitenancy.Init(&multitenancymodels.TypeInput{
GetAllowedDomainsForTenantId: func(tenantId string, userContext supertokens.UserContext) ([]string, error) {
// query your db to get the allowed domain for the input tenantId
// or you can make the tenantId equal to the subdomain itself
return []string{tenantId + ".myapp.com", "myapp.com", "www.myapp.com"}, nil
},
}),
},
})
}from supertokens_python import init, InputAppInfo, SupertokensConfig
from supertokens_python.recipe import multitenancy
from typing import Dict, Any, List
async def get_allowed_domains_for_tenant_id(tenant_id: str, user_context: Dict[str, Any]) -> List[str]:
return [tenant_id + ".myapp.com", "myapp.com", "www.myapp.com"]
init(
app_info=InputAppInfo(
app_name="...",
api_domain="...",
website_domain="...",
),
supertokens_config=SupertokensConfig(
connection_uri="...",
),
framework="django", # Change this to "flask" or "fastapi" if you are using Flask or FastAPI
recipe_list=[
multitenancy.init(
get_allowed_domains_for_tenant_id=get_allowed_domains_for_tenant_id
)
],
)The configuration above tells SuperTokens to add the returned domains to the user’s session claims when they sign in. The SDK can access the claim on the frontend and backend to restrict where the session is used.
4. Share sessions across subdomains (optional)
If users need the same session across multiple subdomains, update the configuration.
Set the sessionTokenFrontendDomain value in the Session recipe to enable this behavior.
If the subdomain and main website domain have different backends on different subdomains, you can also enable sharing of sessions across API domains.
5. Limit session use to the tenant’s subdomain
Use session claim validators on the frontend to restrict session use by subdomain.
Before proceeding, ensure that you have defined the GetAllowedDomainsForTenantId function mentioned above.
This adds the list of allowed domains into the user’s access token payload.
On the frontend, check whether the current subdomain is in the session’s allowed domains.
If it is not, redirect the user to the correct subdomain.
Use the hasAccessToCurrentDomain session validator from the multitenancy recipe.
Make changes to the auth route configuration, as well as to the supertokens-web-js SDK configuration at the root of your application:
This change is in your auth route configuration.
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];
},
},
],
}),
},
});// this goes in the auth route config of your frontend app (once the pre-built UI script has been loaded)
supertokensUISession.init({
override: {
functions: (oI) => ({
...oI,
getGlobalClaimValidators: ({ claimValidatorsAddedByOtherRecipes }) => [
...claimValidatorsAddedByOtherRecipes,
{
...supertokensMultitenancy.AllowedDomainsClaim.validators.hasAccessToCurrentDomain(),
onFailureRedirection: async () => {
let claimValue = await supertokensUISession.getClaimValue({
claim: supertokensMultitenancy.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 the correct subdomain using the values in the AllowedDomainsClaim session claim.
This change goes in the supertokens-web-js SDK configuration at the root of your application:
import Session from "supertokens-web-js/recipe/session";
import { AllowedDomainsClaim } from "supertokens-web-js/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 the correct subdomain using the values in the AllowedDomainsClaim session claim.
import Session from "supertokens-web-js/recipe/session";
import { AllowedDomainsClaim } from "supertokens-web-js/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];
},
},
],
}),
},
});supertokensSession.init({
override: {
functions: (oI) => ({
...oI,
getGlobalClaimValidators: ({ claimValidatorsAddedByOtherRecipes }) => [
...claimValidatorsAddedByOtherRecipes,
{
...supertokensMultitenancy.AllowedDomainsClaim.validators.hasAccessToCurrentDomain(),
onFailureRedirection: async () => {
let claimValue = await supertokensSession.getClaimValue({
claim: supertokensMultitenancy.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 the correct subdomain using the values in the AllowedDomainsClaim session claim.