Paid Feature
This is a paid feature.
For self hosted users, Sign up to get a license key and follow the instructions sent to you by email. Creation of tenants is free on the dev license key.
This feature is already enabled for managed service users. Creation of additional tenant is free on the provided development environment.
Multitenant and Enterprise SSO login
Multitenant login is a feature that lets you customize the login experience for each of your customers. For example, a customer customer1
hosted on customer1.yourdomain.com
can have login with Active Directory
and Google
, and another customer customer2
hosted on customer2.yourdomain.com
can have login with Okta
, Facebook
and magic link based login.
This is also the page that you should see if you want to implement sign in with:
- Okta (
thirdPartyId: "okta"
) - SAML (
thirdPartyId: "boxy-saml"
) - Active Directory (
thirdPartyId: "active-directory"
) - Google Workspaces (
thirdPartyId: "google-workspaces"
) - GitLab (
thirdPartyId: "gitlab"
) - Bitbucket (
thirdPartyId: "bitbucket"
) - Or any other workforce IdP
#
Step 1: Create and configure a new tenant in SuperTokens coreEach tenant can be configured with a unique tenantId
, and the list of third party connections (Active Directory, Google etc..) that should be allowed for them.
You can create a new tenant using our backend SDKs or via a cURL
command to the core.
- Dashboard
- NodeJS
- GoLang
- Python
- cURL
Important
import Multitenancy from "supertokens-node/recipe/multitenancy";
import { FactorIds } from "supertokens-node/recipe/multifactorauth";
async function createNewTenant() {
let resp = await Multitenancy.createOrUpdateTenant("customer1", {
firstFactors: [FactorIds.OTP_PHONE, FactorIds.OTP_EMAIL, FactorIds.LINK_PHONE, FactorIds.LINK_EMAIL, FactorIds.THIRDPARTY]
});
if (resp.createdNew) {
// new tenant was created
} else {
// existing tenant's config was modified.
}
}
In the example above, we have added all factor IDs related to the passwordless recipe. However, you can choose to use a subset of them. The factor IDs are described below:
- ThirdParty:
FactorIds.THIRDPARTY
- Passwordless:
- With email OTP:
FactorIds.OTP_EMAIL
- With SMS OTP:
FactorIds.OTP_PHONE
- With email magic link:
FactorIds.LINK_EMAIL
- With SMS magic link:
FactorIds.LINK_PHONE
- With email OTP:
import (
"github.com/supertokens/supertokens-golang/recipe/multitenancy"
"github.com/supertokens/supertokens-golang/recipe/multitenancy/multitenancymodels"
)
func main() {
tenantId := "customer1"
thirdPartyEnabled := true
passwordlessEnabled := true
resp, err := multitenancy.CreateOrUpdateTenant(tenantId, multitenancymodels.TenantConfig{
ThirdPartyEnabled: &thirdPartyEnabled,
PasswordlessEnabled: &passwordlessEnabled,
})
if err != nil {
// handle error
}
if resp.OK.CreatedNew {
// new tenant was created
} else {
// existing tenant's config was modified.
}
}
- Asyncio
- Syncio
from supertokens_python.recipe.multitenancy.asyncio import create_or_update_tenant
from supertokens_python.recipe.multitenancy.interfaces import TenantConfig
async def some_func():
response = await create_or_update_tenant("customer1", TenantConfig(
third_party_enabled=True,
passwordless_enabled=True,
))
if response.status != "OK":
print("Handle error")
elif response.created_new:
print("new tenant was created")
else:
print("existing tenant's config was modified.")
from supertokens_python.recipe.multitenancy.syncio import create_or_update_tenant
from supertokens_python.recipe.multitenancy.interfaces import TenantConfig
response = create_or_update_tenant("customer1", TenantConfig(
third_party_enabled=True,
passwordless_enabled=True,
))
if response.status != "OK":
print("Handle error")
elif response.created_new:
print("new tenant was created")
else:
print("existing tenant's config was modified.")
- Single app setup
- Multi app setup
- Core version >= 9.1.0
- Core version <= 9.0.2
curl --location --request PUT '/recipe/multitenancy/tenant/v2' \
--header 'api-key: ' \
--header 'Content-Type: application/json' \
--data-raw '{
"tenantId": "customer1",
"firstFactors": ["otp-email", "otp-phone", "link-email", "link-phone", "thirdparty"]
}'
In the example above, we have added all factor IDs related to the passwordless recipe. However, you can choose to use a subset of them. The factor IDs are described below:
- ThirdParty:
thirdparty
- Passwordless:
- With email OTP:
otp-email
- With SMS OTP:
otp-phone
- With email magic link:
link-email
- With SMS magic link:
link-phone
- With email OTP:
curl --location --request PUT '/recipe/multitenancy/tenant' \
--header 'api-key: ' \
--header 'Content-Type: application/json' \
--data-raw '{
"tenantId": "customer1",
"thirdPartyEnabled": true,
"passwordlessEnabled": true
}'
- Core version >= 9.1.0
- Core version <= 9.0.2
curl --location --request PUT '/recipe/multitenancy/tenant/v2' \
--header 'api-key: ' \
--header 'Content-Type: application/json' \
--data-raw '{
"tenantId": "customer1",
"firstFactors": ["otp-email", "otp-phone", "link-email", "link-phone", "thirdparty"]
}'
In the example above, we have added all factor IDs related to the passwordless recipe. However, you can choose to use a subset of them. The factor IDs are described below:
- ThirdParty:
thirdparty
- Passwordless:
- With email OTP:
otp-email
- With SMS OTP:
otp-phone
- With email magic link:
link-email
- With SMS magic link:
link-phone
- With email OTP:
curl --location --request PUT '/recipe/multitenancy/tenant' \
--header 'api-key: ' \
--header 'Content-Type: application/json' \
--data-raw '{
"tenantId": "customer1",
"thirdPartyEnabled": true,
"passwordlessEnabled": true
}'
Once a tenant is created, add their thirdparty providers as shown below.
- Dashboard
- NodeJS
- GoLang
- Python
- cURL
Important
import Multiteancy from "supertokens-node/recipe/multitenancy";
async function createTenant() {
let resp = await Multiteancy.createOrUpdateThirdPartyConfig("customer1", {
thirdPartyId: "active-directory",
name: "Active Directory",
clients: [{
clientId: "...",
clientSecret: "...",
}],
oidcDiscoveryEndpoint: "https://login.microsoftonline.com/<directoryId>/v2.0/.well-known/openid-configuration",
});
if (resp.createdNew) {
// Active Directory added to customer1
} else {
// Existing active directory config overwritten for customer1
}
}
import (
"github.com/supertokens/supertokens-golang/recipe/multitenancy"
"github.com/supertokens/supertokens-golang/recipe/thirdparty/tpmodels"
)
func main() {
tenantId := "customer1"
resp, err := multitenancy.CreateOrUpdateThirdPartyConfig(tenantId, tpmodels.ProviderConfig{
ThirdPartyId: "active-directory",
Name: "Active Directory",
Clients: []tpmodels.ProviderClientConfig{
{
ClientID: "...",
ClientSecret: "...",
},
},
OIDCDiscoveryEndpoint: "https://login.microsoftonline.com/<directoryId>/v2.0/.well-known/openid-configuration",
}, nil, nil)
if err != nil {
// handle error
}
if resp.OK.CreatedNew {
// Active Directory added to customer1
} else {
// Existing active directory config overwritten for customer1
}
}
- Asyncio
- Syncio
from supertokens_python.recipe.multitenancy.asyncio import create_or_update_third_party_config
from supertokens_python.recipe.thirdparty.provider import ProviderConfig, ProviderClientConfig
async def update_tenant():
result = await create_or_update_third_party_config(
"customer1",
config=ProviderConfig(
third_party_id="active-directory",
name="Active Directory",
clients=[
ProviderClientConfig(
client_id="...",
client_secret="...",
)
],
oidc_discovery_endpoint="https://login.microsoftonline.com/<directoryId>/v2.0/.well-known/openid-configuration",
),
)
if result.status != "OK":
print("Error adding active directory to tenant")
elif result.created_new:
print("Active directory was added to the tenant")
else:
print("Existing tenant's active directory config was modified")
from supertokens_python.recipe.multitenancy.syncio import create_or_update_third_party_config
from supertokens_python.recipe.thirdparty.provider import ProviderConfig, ProviderClientConfig
result = create_or_update_third_party_config(
"customer1",
config=ProviderConfig(
third_party_id="active-directory",
name="Active Directory",
clients=[
ProviderClientConfig(
client_id="...",
client_secret="...",
)
]
),
)
if result.status != "OK":
print("Error creating or updating tenant")
elif result.created_new:
print("New tenant was created")
else:
print("Existing tenant's config was modified")
- Single app setup
- Multi app setup
curl --location --request PUT '/<TENANT_ID>/recipe/multitenancy/config/thirdparty' \
--header 'api-key: ' \
--header 'Content-Type: application/json' \
--data-raw '{
"config": {
"thirdPartyId": "active-directory",
"name": "Active Directory",
"clients": [
{
"clientId": "...",
"clientSecret": "..."
}
],
"oidcDiscoveryEndpoint": "https://login.microsoftonline.com/<directoryId>/v2.0/.well-known/openid-configuration"
}
}'
curl --location --request PUT '/<TENANT_ID>/recipe/multitenancy/config/thirdparty' \
--header 'api-key: ' \
--header 'Content-Type: application/json' \
--data-raw '{
"config": {
"thirdPartyId": "active-directory",
"name": "Active Directory",
"clients": [
{
"clientId": "...",
"clientSecret": "..."
}
],
"oidcDiscoveryEndpoint": "https://login.microsoftonline.com/<directoryId>/v2.0/.well-known/openid-configuration"
}
}'
important
The above shows how to add an Active Directory config for the customer1
tenant. You can see the config structure for all the in built providers on this page.
#
Step 2: Build your multi tenant a UX flowThe most common multi tenant flows are:
- Tenants use a common domain to login: All tenants login using the same page (for example,
example.com/auth
) and are optionally redirected to their sub domain post login. At the start of the login flow, the customer will have to input their tenantId / workspace URL / identifier - as defined by you, and the login methods shown would be based on their tenantId. - Tenants use their sub domain to login: Here, each tenant has a sub domain assigned to them (for example
customer1.example.com
,customer2.example.com
, ...), and they would visit their sub domain to login and access their app. Each sub domain's login experience may be different (as defined by you or the tenant).
SuperTokens is flexible enough to allow other forms of UX as well, but since the above two flow are most common, we provide dedicated docs for them (see the links above).