File length: 25808 # Authentication - Enterprise Login - Common domain login Source: https://supertokens.com/docs/authentication/enterprise/common-domain-login ## 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. :::info Important You can find an example app for this setup with the **pre-built UI** on [the GitHub example directory](https://github.com/supertokens/supertokens-auth-react/tree/master/examples/with-one-login-many-subdomains). The app is setup to have three tenants: - `tenant1`: Login with `emailpassword` + Google sign in - `tenant2`: Login with `emailpassword` - `tenant3`: Login with passwordless + GitHub sign in You can also generate a demo app using the following command: ```bash 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](/docs/quickstart/introduction). You also need to create the tenants that your application requires. View the [previous tutorial](/docs/authentication/enterprise/initial-setup) for more information on how to do this. ## Steps ### 1. Ask for the tenant ID on the login page If you have [followed the pre-built UI setup](/docs/quickstart/frontend-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`. :::warning no-title You have to [create tenants](/docs/authentication/enterprise/initial-setup) before you can complete this step. ::: ```tsx 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 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 prefix to them, otherwise they would // render on ^{form_websiteBasePath} path. return ( {getSuperTokensRoutesForReactRouterDom( reactRouterDom, [EmailPasswordPreBuiltUI], "" )} ); } else { return (
{ // this value will be read by SuperTokens as shown in the next steps. localStorage.setItem("tenantId", inputTenantId); }}>

Enter your organisation's name:

setInputTenantId(e.target.value)} />
); } }; ```
```tsx export const AuthPage = () => { 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 ) { return getRoutingComponent([EmailPasswordPreBuiltUI]); } else { return (
{ // this value will be read by SuperTokens as shown in the next steps. localStorage.setItem("tenantId", inputTenantId); }}>

Enter your organisation's name:

setInputTenantId(e.target.value)} />
); } }; ```
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`. :::info Important The `AuthPage` component should render to show on `/*` 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. :::
:::info Caution No code snippet provided here, however, if you visit the auth component, you see that the pre-built UI renders in the `"supertokensui"` `div` element on page load. The logic here needs to change to first check if the user has provided the `tenantId`. If they have, the SuperTokens UI renders as usual. If they have not, a simple UI renders which asks the user for their tenant id and saves that in `localstorage`. Switch to the React code tab here to see the implementation in React, and a similar logic applies here. :::
You need to build a UI that asks the user to enter their `tenantId` or Organisation name (which can serve as the tenant ID). The input value is then utilized in function calls, as seen below. Once you have the user's tenant ID, you can fetch their list of configured providers and render the third party login buttons accordingly: ```tsx 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 } } ``` ```tsx 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 } } ``` - In the code snippet above, the login methods for the `tenantId` fetch. - We then render the login UI buttons based on the configured `thirdPartyId`s in the response ```bash curl --location --request GET '/loginmethods' ``` 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 appear on the frontend. ### 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`. ```tsx SuperTokens.init({ appInfo: { appName: "...", apiDomain: "...", websiteDomain: "...", apiBasePath: "...", websiteBasePath: "..." }, // highlight-next-line usesDynamicLoginMethods: true, recipeList: [ // highlight-start Multitenancy.init({ override: { functions: (oI) => { return { ...oI, getTenantId: (input) => { let tid = localStorage.getItem("tenantId"); return tid === null ? undefined : tid; } } } } }) // highlight-end // other recipes... ] }); ``` :::info Important 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. ::: ```tsx // this goes in the auth route config of your frontend app (once the pre-built UI script has been loaded) supertokensUIInit({ appInfo: { appName: "...", apiDomain: "...", websiteDomain: "...", apiBasePath: "...", websiteBasePath: "..." }, // highlight-next-line usesDynamicLoginMethods: true, recipeList: [ // highlight-start supertokensUIMultitenancy.init({ override: { functions: (oI) => { return { ...oI, getTenantId: (input) => { let tid = localStorage.getItem("tenantId"); return tid === null ? undefined : tid; } } } } }) // highlight-end // other recipes... ] }); ``` :::info Important 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. ::: Initialize the multi tenancy recipe with the following callback defined. You can get the value of `tenantId` from wherever you had stored it when asking the user for its value. ```tsx SuperTokens.init({ appInfo: { appName: "...", apiDomain: "...", }, recipeList: [ // highlight-start Multitenancy.init({ override: { functions: (oI) => { return { ...oI, getTenantId: (input) => { let tid = localStorage.getItem("tenantId"); return tid === null ? undefined : tid; } } } } }) // highlight-end // other recipes... ] }); ``` ```tsx supertokens.init({ appInfo: { appName: "...", apiDomain: "...", }, recipeList: [ // highlight-start supertokensMultitenancy.init({ override: { functions: (oI) => { return { ...oI, getTenantId: (input) => { let tid = localStorage.getItem("tenantId"); return tid === null ? undefined : tid; } } } } }) // highlight-end // other recipes... ] }); ``` All the steps for mobile app login are similar to the [social login steps](../../custom-ui/thirdparty-login). However, when you are calling the sign in up API, you also need to pass in the `tenantId` in the request path. An example of this appears below: ```bash curl --location --request POST '/signinup' \ --header 'Content-Type: application/json' \ --data-raw '{ "thirdPartyId": "...", "clientType": "...", "oAuthTokens": { "access_token": "...", "id_token": "..." }, }' ``` ### 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. ```tsx SuperTokens.init({ appInfo: { apiDomain: "...", appName: "...", websiteDomain: "..." }, recipeList: [ // highlight-start 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"] } }), // highlight-end // other recipes... ] }) ``` ```go SuperTokens.init({ appInfo: { apiDomain: "...", appName: "...", websiteDomain: "...", }, // highlight-start 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; }, // highlight-end recipeList: [ /* Recipe init here... */ ] }); ``` ```tsx // this goes in the auth route config of your frontend app (once the pre-built UI script has been loaded) supertokensUIInit({ appInfo: { apiDomain: "...", appName: "...", websiteDomain: "...", }, // highlight-start getRedirectionURL: async (context) => { if (context.action === "SUCCESS" && context.newSessionCreated) { let claimValue: string[] | undefined = await supertokensUISession.getClaimValue({ claim: supertokensUIMultitenancy.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; }, // highlight-end recipeList: [ /* Recipe init here... */ ] }); ``` On the frontend, once the user has completed login, you can read the domain that they have access to (from their session) and redirect them accordingly. ```tsx async function redirectToSubDomain() { if (await Session.doesSessionExist()) { 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 } } else { window.location.href = "/auth"; } } ``` ```tsx async function redirectToSubDomain() { if (await supertokensSession.doesSessionExist()) { let claimValue: string[] | undefined = await supertokensSession.getClaimValue({ claim: supertokensMultitenancy.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 } } else { window.location.href = "/auth"; } } ``` - The `AllowedDomainsClaim` claim is auto added to the session by the backend SDK if you provide the `GetAllowedDomainsForTenantId` 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](/docs/post-authentication/session-management/share-session-across-sub-domains). 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](/docs/post-authentication/session-management/advanced-workflows/multiple-api-endpoints). ### 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. ```tsx 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](/docs/additional-verification/session-verification/protect-frontend-routes), 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). You need to 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. ```tsx // 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]; }, }, ], }), }, }) ``` This change goes in the `supertokens-web-js` SDK configuration at the root of your application: ```tsx 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](../sessions/protecting-frontend-routes), 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). ```tsx 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]; }, }, ], }), }, }) ``` ```tsx 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](../sessions/protecting-frontend-routes#verifying-the-claims-of-a-session--cust), 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). ---