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.
Users and tenants
When a user is created, they assigned to the tenantId using which they signed up. This means that that user can only login into that tenant. SuperTokens allows you to assign a user ID to multiple tenants as long as that user's email / phone number is unique for that login method, for each of the new tenants. Once associated with multiple tenants, that user can login to each of the tenants they have been assigned to.
For example, if a user signs up with email password login in the public
tenant with email user@example.com
, they can be assigned to another tenant (t1
for example), as long as t1
doesn't already have an email password user with the same email (i.e. user@example.com
).
In order to associate a user with a tenant, you can call the following API:
- NodeJS
- GoLang
- Python
- cURL
Important
import Multitenancy from "supertokens-node/recipe/multitenancy";
import {RecipeUserId} from "supertokens-node";
async function addUserToTenant(recipeUserId: RecipeUserId, tenantId: string) {
let resp = await Multitenancy.associateUserToTenant(tenantId, recipeUserId);
if (resp.status === "OK") {
// User is now associated with tenant
} else if (resp.status === "UNKNOWN_USER_ID_ERROR") {
// The provided user ID was not one that signed up using one of SuperTokens' auth recipes.
} else if (resp.status === "EMAIL_ALREADY_EXISTS_ERROR") {
// This means that the input user is one of passwordless or email password logins, and the new tenant already has a user with the same email for that login method.
} else if (resp.status === "PHONE_NUMBER_ALREADY_EXISTS_ERROR") {
// This means that the input user is a passwordless user and the new tenant already has a user with the same phone number, for passwordless login.
} else if (resp.status === "ASSOCIATION_NOT_ALLOWED_ERROR") {
// This can happen if using account linking along with multi tenancy. One example of when this
// happens if if the target tenant has a primary user with the same email / phone numbers
// as the current user.
} else {
// status is THIRD_PARTY_USER_ALREADY_EXISTS_ERROR
// This means that the input user had already previously signed in with the same third party provider (e.g. Google) for the new tenant.
}
}
import (
"github.com/supertokens/supertokens-golang/recipe/multitenancy"
)
func main() {
tenantId := "customer1"
userID := "user1"
resp, err := multitenancy.AssociateUserToTenant(tenantId, userID)
if err != nil {
// handle error
}
if resp.OK != nil {
// User is now associated with tenant
} else if resp.UnknownUserIdError != nil {
// The provided user ID was not one that signed up using one of SuperTokens' auth recipes.
} else if resp.EmailAlreadyExistsError != nil {
// This means that the input user is one of passwordless or email password logins, and the new tenant already has a user with the same email for that login method.
} else if resp.PhoneNumberAlreadyExistsError != nil {
// This means that the input user is a passwordless user and the new tenant already has a user with the same phone number, for passwordless login.
} else {
// status is ThirdPartyUserAlreadyExistsError
// This means that the input user had already previously signed in with the same third party provider (e.g. Google) for the new tenant.
}
}
- Asyncio
- Syncio
from supertokens_python.recipe.multitenancy.asyncio import associate_user_to_tenant
from supertokens_python.recipe.multitenancy.interfaces import AssociateUserToTenantUnknownUserIdError, AssociateUserToTenantEmailAlreadyExistsError, AssociateUserToTenantPhoneNumberAlreadyExistsError
async def some_func():
response = await associate_user_to_tenant("customer1", "user1")
if response.status == "OK":
print("User is now associated with tenant")
elif isinstance(response, AssociateUserToTenantUnknownUserIdError):
print("The provided user ID was not one that signed up using one of SuperTokens' auth recipes.")
elif isinstance(response, AssociateUserToTenantEmailAlreadyExistsError):
print("This means that the input user is one of passwordless or email password logins, and the new tenant already has a user with the same email for that login method.")
elif isinstance(response, AssociateUserToTenantPhoneNumberAlreadyExistsError):
print("This means that the input user is a passwordless user and the new tenant already has a user with the same phone number, for passwordless login.")
else:
print("status is ThirdPartyUserAlreadyExistsError")
print("This means that the input user had already previously signed in with the same third party provider (e.g. Google) for the new tenant.")
from supertokens_python.recipe.multitenancy.syncio import associate_user_to_tenant
from supertokens_python.recipe.multitenancy.interfaces import AssociateUserToTenantUnknownUserIdError, AssociateUserToTenantEmailAlreadyExistsError, AssociateUserToTenantPhoneNumberAlreadyExistsError
response = associate_user_to_tenant("customer1", "user1")
if response.status == "OK":
print("User is now associated with tenant")
elif isinstance(response, AssociateUserToTenantUnknownUserIdError):
print("The provided user ID was not one that signed up using one of SuperTokens' auth recipes.")
elif isinstance(response, AssociateUserToTenantEmailAlreadyExistsError):
print("This means that the input user is one of passwordless or email password logins, and the new tenant already has a user with the same email for that login method.")
elif isinstance(response, AssociateUserToTenantPhoneNumberAlreadyExistsError):
print("This means that the input user is a passwordless user and the new tenant already has a user with the same phone number, for passwordless login.")
else:
print("status is ThirdPartyUserAlreadyExistsError")
print("This means that the input user had already previously signed in with the same third party provider (e.g. Google) for the new tenant.")
- Single app setup
- Multi app setup
curl --location --request POST '/<TENANT_ID>/recipe/multitenancy/tenant/user \
--header 'api-key: ' \
--header 'Content-Type: application/json' \
--data-raw '{
"userId": "..."
}'
curl --location --request POST '/<TENANT_ID>/recipe/multitenancy/tenant/user \
--header 'api-key: ' \
--header 'Content-Type: application/json' \
--data-raw '{
"userId": "..."
}'
In the above code, we are associating userId
with the tenant with ID TENANT_ID
. The output of the above API will have the following status
response:
"OK"
: User association with tenant was successful"UNKNOWN_USER_ID_ERROR"
: The provided user ID was not one that signed up using one of SuperTokens' auth recipes."EMAIL_ALREADY_EXISTS_ERROR"
: This means that the input user is one of passwordless or email password logins, and the new tenant already has a user with the same email for that login method."PHONE_NUMBER_ALREADY_EXISTS_ERROR"
: This means that the input user is a passwordless user and the new tenant already has a user with the same phone number, for passwordless login."THIRD_PARTY_USER_ALREADY_EXISTS_ERROR"
: This means that the input user had already previously signed in with the same third party provider (e.g. Google) for the new tenant.
You can even remove a user's access from a tenant using the API call shown below. In fact, you can remove a user from all tenants that they have access to, and the user and their metadata will still be in the system, however they will not be able to login into any tenant. To remove a user from a tenant, call the following API:
- NodeJS
- GoLang
- Python
- cURL
Important
import Multitenancy from "supertokens-node/recipe/multitenancy";
import {RecipeUserId} from "supertokens-node";
async function removeUserFromTeannt(recipeUserId: RecipeUserId, tenantId: string) {
let resp = await Multitenancy.disassociateUserFromTenant(tenantId, recipeUserId);
if (resp.wasAssociated) {
// User was removed from tenant
} else {
// User was never a part of the tenant anyway
}
}
import (
"github.com/supertokens/supertokens-golang/recipe/multitenancy"
)
func main() {
tenantId := "customer1"
userID := "user1"
resp, err := multitenancy.DisassociateUserFromTenant(tenantId, userID)
if err != nil {
// handle error
}
if resp.OK.WasAssociated {
// User was removed from tenant
} else {
// User was never a part of the tenant anyway
}
}
- Asyncio
- Syncio
from supertokens_python.recipe.multitenancy.asyncio import dissociate_user_from_tenant
async def some_func():
response = await dissociate_user_from_tenant("customer1", "user1")
if response.was_associated:
print("User was removed from tenant")
else:
print("User was never a part of the tenant anyway")
from supertokens_python.recipe.multitenancy.syncio import dissociate_user_from_tenant
response = dissociate_user_from_tenant("customer1", "user1")
if response.status != "OK":
print("Handle error")
if response.was_associated:
print("User was removed from tenant")
else:
print("User was never a part of the tenant anyway")
- Single app setup
- Multi app setup
curl --location --request POST '/<TENANT_ID>/recipe/multitenancy/tenant/user/remove \
--header 'api-key: ' \
--header 'Content-Type: application/json' \
--data-raw '{
"userId": "..."
}'
curl --location --request POST '/<TENANT_ID>/recipe/multitenancy/tenant/user/remove \
--header 'api-key: ' \
--header 'Content-Type: application/json' \
--data-raw '{
"userId": "..."
}'
important
- Users can only be shared across tenants and not across apps.
- If your app has two tenants, that are in different database locations, then you cannot share users between them.