---
title: Implement subdomain login
description: Authenticate users across different tenants through different subdomains.
sidebar:
  order: 50
---

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

:::note[Throughout this page, assume that a tenant's ID matches its subdomain. If the subdomain assigned to a tenant is `customer1.example.com`, then its `tenantId` is `customer1`.]

An example app for this setup with the **pre-built UI** is available on [the GitHub example directory](https://github.com/supertokens/supertokens-auth-react/tree/master/examples/with-one-login-per-subdomain). The app is setup to have three tenants:
- `tenant1.example.com`: Login with `emailpassword` + Google sign in
- `tenant2.example.com`: Login with `emailPassword`
- `tenant3.example.com`: Login with passwordless + GitHub sign in
:::

## Before you start

<PaidFeatureCallout />

The tutorial assumes that you already have a working application integrated with **SuperTokens**.
If you have not, please check the [Quickstart Guide](/quickstart).

Your application also needs you to create the tenants it requires.
View the [previous tutorial](/authentication/enterprise/initial-setup) for more information on how to do this.


## Steps

<UITypeSwitch />

### 1. Change the CORS settings and `websiteDomain`

:::warning
You have to [create tenants](/authentication/enterprise/initial-setup) before you can complete this step.
:::

#### 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`


<VariantContent storageKey="ui-type" value="prebuilt">

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.


<CodeGroup group="frontend-prebuilt-ui">
<Tab title="Reactjs" value="reactjs">
```tsx
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];
            },
          };
        },
      },
    }),
  ],
});
```
</Tab>
<Tab title="Angular" value="angular">
```tsx check=false reason="This example omits surrounding application and SuperTokens configuration."
// 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];
            },
          };
        },
      },
    }),
  ],
});
```
</Tab>
</CodeGroup>

</VariantContent>

<VariantContent storageKey="ui-type" value="custom">


You can fetch the user's login methods based on their tenant ID, which you can derive from the current subdomain, as shown below.


<CodeGroup group="frontend-custom-ui">
<Tab title="Web" value="web">
<DependentContent group="install-method" label="Installation method">
<ContentOption title="npm" value="npm">
```tsx
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
  }
}
```
</ContentOption>
<ContentOption title="Script tag" value="script-tag">
```tsx
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
  }
}
```
</ContentOption>
</DependentContent>
</Tab>
<Tab title="Mobile" value="mobile">
```bash
curl --location --request GET '<YOUR_API_DOMAIN>/auth/loginmethods'
```
</Tab>
</CodeGroup>

<DependentContent passive group="frontend-custom-ui">
<ContentOption title="Mobile" value="mobile">
The response body from the API call has a `status` property in it:

- `status: "OK"`: The `recipes` field 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.
</ContentOption>
</DependentContent>




<DependentContent passive group="frontend-custom-ui">
<ContentOption title="Web" value="web">
You also need to initialize the multitenancy recipe with the following callback. You can get the tenant ID from the subdomain as shown below.
</ContentOption>
<ContentOption title="Mobile" value="mobile">
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](/authentication/social/initial-setup#2-add-the-login-ui). When calling the sign in up API, also pass in the `tenantId` in the request path. An example of this appears below:
</ContentOption>
</DependentContent>

<CodeGroup group="frontend-custom-ui">
<Tab title="Web" value="web">
<DependentContent group="install-method" label="Installation method">
<ContentOption title="npm" value="npm">
```tsx
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...
  ],
});
```
</ContentOption>
<ContentOption title="Script tag" value="script-tag">
```tsx check=false reason="This example omits surrounding application and SuperTokens configuration."
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...
  ],
});
```
</ContentOption>
</DependentContent>
</Tab>
<Tab title="Mobile" value="mobile">
```bash
curl --location --request POST '<YOUR_API_DOMAIN>/auth/signinup' \
--header 'Content-Type: application/json' \
--data-raw '{
    "thirdPartyId": "...",
    "clientType": "...",
    "oAuthTokens": {
        "access_token": "...",
        "id_token": "..."
    },
}'
```
</Tab>
</CodeGroup>



</VariantContent>


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


<CodeGroup group="backend-language">
<Tab title="Node.js" value="nodejs">
```tsx
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...
  ],
});
```
</Tab>
<Tab title="Go" value="go">
```go
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
				},
			}),
		},
	})
}
```
</Tab>
<Tab title="Python" value="python">
```python
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
        )
    ],
)
```
</Tab>
</CodeGroup>

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.

:::warning[Domain checks are not tenant authorization]
`AllowedDomainsClaim` and `hasAccessToCurrentDomain` restrict session use by hostname. They do not prove that the user
belongs to an organization, enforce CORS or allowed browser origins, authorize access to business data, or provide
complete tenant isolation. After authentication, verify the user's tenant membership. On every business-data access,
the backend must derive the tenant from trusted session data and enforce application-level tenant authorization. Do
not trust a tenant ID, hostname, or claim supplied by the browser as authorization.
:::

### 4. Share sessions across subdomains (optional)

If users need the same session across multiple subdomains, update the configuration.
Set the [`sessionTokenFrontendDomain` value ](/post-authentication/session-management/share-session-across-sub-domains) 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](/post-authentication/session-management/advanced-workflows/multiple-api-endpoints).


:::note[Even if they visit the main domain (logged in via `a.example.com`, and visit `example.com`), the frontend app there can detect if the user has a session or not.]
This only shows that a session exists. The domain validator below restricts where that session is used; application-level tenant authorization is still required.
:::

### 5. Limit session use to the tenant's subdomain

Use [session claim validators](/additional-verification/session-verification/claim-validation#using-session-claims) 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.


<VariantContent storageKey="ui-type" value="prebuilt">

<DependentContent passive group="frontend-prebuilt-ui">
<ContentOption title="Angular" value="angular">
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.
</ContentOption>
</DependentContent>

<CodeGroup group="frontend-prebuilt-ui">
<Tab title="Reactjs" value="reactjs">
```tsx
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];
          },
        },
      ],
    }),
  },
});
```
</Tab>
<Tab title="Angular" value="angular">
```tsx check=false reason="This example omits surrounding application and SuperTokens configuration."
// 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];
          },
        },
      ],
    }),
  },
});
```
</Tab>
</CodeGroup>

<DependentContent passive group="frontend-prebuilt-ui">
<ContentOption title="Reactjs" value="reactjs">
Above, in `Session.init` on the frontend, add the `hasAccessToCurrentDomain` claim validator to the global validators. This means that [whenever a route requires protection](/additional-verification/session-verification/protect-frontend-routes), it checks if `hasAccessToCurrentDomain` has passed. If not, SuperTokens redirects the user to the correct subdomain using the values in the `AllowedDomainsClaim` session claim.
</ContentOption>
<ContentOption title="Angular" value="angular">
This change goes in the `supertokens-web-js` SDK configuration at the root of your application:
</ContentOption>
</DependentContent>

<CodeGroup passive group="frontend-prebuilt-ui">
<Tab title="Angular" value="angular">
```tsx
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];
          },
        },
      ],
    }),
  },
});
```
</Tab>
</CodeGroup>

<DependentContent passive group="frontend-prebuilt-ui">
<ContentOption title="Angular" value="angular">
Above, in `Session.init` on the frontend, add the `hasAccessToCurrentDomain` claim validator to the global validators. This means that [whenever a route requires protection](/additional-verification/session-verification/protect-frontend-routes), it checks if `hasAccessToCurrentDomain` has passed. If not, SuperTokens redirects the user to the correct subdomain using the values in the `AllowedDomainsClaim` session claim.
</ContentOption>
</DependentContent>

</VariantContent>

<VariantContent storageKey="ui-type" value="custom">

<CodeGroup group="install-method">
<Tab title="npm" value="npm">
```tsx
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];
          },
        },
      ],
    }),
  },
});
```
</Tab>
<Tab title="Script tag" value="script-tag">
```tsx check=false reason="This example omits surrounding application and SuperTokens configuration."
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];
          },
        },
      ],
    }),
  },
});
```
</Tab>
</CodeGroup>

Above, in `Session.init` on the frontend, add the `hasAccessToCurrentDomain` claim validator to the global validators. This means that [whenever a route requires protection](/additional-verification/session-verification/protect-frontend-routes#check-the-claims-of-a-session), it checks if `hasAccessToCurrentDomain` has passed. If not, SuperTokens redirects the user to the correct subdomain using the values in the `AllowedDomainsClaim` session claim.

</VariantContent>


---

## See also

<CardGroup cols={3}>
  <Card title="Create and configure tenants" href="/authentication/enterprise/manage-tenants" />
  <Card title="Implement common domain login" href="/authentication/enterprise/common-domain-login" />
  <Card title="SAML" href="/authentication/enterprise/saml" />
  <Card title="Create and configure apps" href="/authentication/enterprise/manage-apps" />
</CardGroup>
