Skip to content
Esc
navigateopen⌘Jpreview
Dashboard
On this page

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

Enable paid features

This feature is only available to paid users. Follow the instructions below to enable it.

Managed Service

  1. Sign in to the SuperTokens dashboard.
  2. Select the managed service option from the service type select component.
  3. Select your core instance from the next elemenet or create a new one.
  4. Open Features sub-page and enable the required ones.

Self Hosted

  1. Sign in to the SuperTokens dashboard.
  2. Select the self-hosted option from the service type select component.
  3. Select your license key from the next elemenet or create a new one. Then enable the required features.
  4. If the key is not yet configured, add it to your Core service. If your Core already uses this key, no configuration changes are required.

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

UI type

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:

  1. Set usesDynamicLoginMethods to true. 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.
  2. Initialize the Multitenancy recipe and provide the getTenantId configuration 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];
            },
          };
        },
      },
    }),
  ],
});

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:

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.


See also

API reference

API schema and response details