Skip to main content

Manage tenants

Discover different APIs that can help you work with tenants.

Create a new tenant

import Multitenancy from "supertokens-node/recipe/multitenancy";

async function createNewTenant() {
let resp = await Multitenancy.createOrUpdateTenant("customer1", {
firstFactors: ["emailpassword", "thirdparty", "otp-email", "otp-phone", "link-phone", "link-email"]
});

if (resp.createdNew) {
// Tenant created successfully
} else {
// Existing tenant's config was modified.
}
}

The snippet creates a new tenant with the id "customer1". It enables the email password, third party and passwordless login methods for this tenant. You can also disable any of these by not including them in the firstFactors input. If firstFactors is not specified, by default, the system does not enable any of the login methods.

If you set firstFactors to null the SDK uses any of the login methods.

The built-in Factor IDs available for firstFactors include:

Authentication TypeFactor ID
Email password authemailpassword
Social login / enterprise SSO auththirdparty
Passwordless - Email OTPotp-email
Passwordless - SMS OTPotp-phone
Passwordless - Email magic linklink-email
Passwordless - SMS magic linklink-phone

Update a tenant

You can also configure a tenant to have different configurations per the core's config.yaml or docker environment variables. Below is how you can specify the configuration, when creating or modifying a tenant:

import Multitenancy from "supertokens-node/recipe/multitenancy";

async function createNewTenant() {

let resp = await Multitenancy.createOrUpdateTenant("customer1", {
coreConfig: {
"email_verification_token_lifetime": 7200000,
"password_reset_token_lifetime": 3600000,
"postgresql_connection_uri": "postgresql://localhost:5432/db2",
}
});

if (resp.createdNew) {
// new tenant was created
} else {
// existing tenant's config was modified.
}
}

In the above example, the system assigns different values for certain configurations for customer1 tenant. All other configurations inherit from the base configuration.

Notice the postgresql_connection_uri. This allows you to achieve data isolation on a tenant level. This configuration is not required. If not provided, the database stores the tenant's information as specified in the core's configuration. It is still a different user pool though.

Get tenant details

Once you have set the configs for a specific tenant, you can fetch the tenant info as shown below:

import Multitenancy from "supertokens-node/recipe/multitenancy";

async function getTenant(tenantId: string) {

let resp = await Multitenancy.getTenant(tenantId);

if (resp === undefined) {
// tenant does not exist
} else {
let coreConfig = resp.coreConfig;

let firstFactors = resp.firstFactors;

let configuredThirdPartyProviders = resp.thirdParty.providers;
}
}

The returned coreConfig is the same as what you set when creating / updating the tenant. The rest of the core configurations for this tenant inherit from the app's (or the public tenant) configuration. The public tenant, for the public app inherits its configurations from the config.yaml / docker environment variables values.

List all the tenants of an app

import Multitenancy from "supertokens-node/recipe/multitenancy";

async function listAllTenants() {

let resp = await Multitenancy.listAllTenants();
let tenants = resp.tenants;

tenants.forEach(tenant => {
let coreConfig = tenant.coreConfig;

let firstFactors = tenant.firstFactors;

let configuredThirdPartyProviders = tenant.thirdParty.providers;
});
}

The value of firstFactors can be as follows:

  • undefined: The core enables all login methods, and any auth recipe initialized in the backend SDK works.
  • [] (empty array): The tenant does not enable any login methods.
  • a non-empty array: The tenant enables only the login methods in the array.

Add a custom third-party provider to a tenant

If you can't find a provider in the built-in list, you can add your own custom implementation. This page shows you how to do that on a per tenant basis.

Note

If you think that SuperTokens should support this provider by default, make sure to let the team know on GitHub.

Once you have created a tenant, you want to call the API / function to create a new provider for the tenant as shown below.

Using OAuth endpoints

import Multiteancy from "supertokens-node/recipe/multitenancy";

async function createTenant() {
let resp = await Multiteancy.createOrUpdateThirdPartyConfig("customer1", {
thirdPartyId: "custom",
name: "Custom Provider",
clients: [{
clientId: "...",
clientSecret: "...",
scope: ["email", "profile"]
}],
authorizationEndpoint: "https://example.com/oauth/authorize",
authorizationEndpointQueryParams: { // optional
"someKey1": "value1",
"someKey2": null,
},
tokenEndpoint: "https://example.com/oauth/token",
tokenEndpointBodyParams: {
"someKey1": "value1",
},
userInfoEndpoint: "https://example.com/oauth/userinfo",
userInfoMap: {
fromUserInfoAPI: {
userId: "id",
email: "email",
emailVerified: "email_verified",
}
}
});

if (resp.createdNew) {
// custom provider added to tenant
} else {
// existing custom provider config overwritten for tenant
}
}

You can see all the options in the CDI documentation.

FieldDescriptionExample
tenantIdUnique ID that identifies the tenant. If not specified, defaults to "public""customer1"
thirdPartyIdUnique ID for identifying the provider"google"
nameDisplay name used for the login button UI"XYZ" → displays "Login using XYZ"
clientsArray of client credentials/settings. Can contain multiple items for different client types (web/mobile)Contains clientId, clientSecret, and optional clientType
authorizationEndpointURL for user login"https://accounts.google.com/o/oauth2/v2/auth"
authorizationEndpointQueryParamsOptional configuration to modify query params
tokenEndpointAPI endpoint for exchanging Authorization Code"https://oauth2.googleapis.com/token"
tokenEndpointBodyParamsOptional configuration to modify request body
userInfoEndpointAPI endpoint that provides user information"https://www.googleapis.com/oauth2/v1/userinfo"
userInfoMapMaps provider's JSON response to user info fields. Use dot notation to map nested fields: user.id{ userId: "id", email: "email", emailVerified: "email_verified" }

Using OpenID Connect endpoints

If the provider is Open ID Connect (OIDC) compatible, you can provide a URL for the OIDCDiscoverEndpoint configuration. The backend SDK automatically discovers authorization endpoint, token endpoint and the user info endpoint by querying the <OIDCDiscoverEndpoint>/.well-known/openid-configuration.

import Multiteancy from "supertokens-node/recipe/multitenancy";

async function createTenant() {
let resp = await Multiteancy.createOrUpdateThirdPartyConfig("customer1", {
thirdPartyId: "custom",
name: "Custom Provider",
clients: [{
clientId: "...",
clientSecret: "...",
scope: ["email", "profile"]
}],
oidcDiscoveryEndpoint: "https://example.com/.well-known/openid-configuration",
authorizationEndpointQueryParams: { // optional
"someKey1": "value1",
"someKey2": null,
},
userInfoMap: {
fromIdTokenPayload: {
userId: "id",
email: "email",
emailVerified: "email_verified",
}
}
});

if (resp.createdNew) {
// custom provider added to tenant
} else {
// existing custom provider config overwritten for tenant
}
}

You can see all the options in the CDI documentation.

FieldDescription
tenantIdUnique ID that identifies the tenant. If not specified, defaults to "public"
thirdPartyId, name, clientsConfiguration values similar to OAuth endpoints method
userInfoMap.fromIdTokenPayloadMaps user info from the ID token payload
userInfoMap.fromUserInfoAPIOptional mapping from user info API. You can combine it with ID token payload mapping

Add a user to a tenant

When a user creates an account, they receive a tenantId to sign up. This means that the user can only log in to that tenant. SuperTokens allows you to assign a user ID to multiple tenants. This is possible as long as that user's email or phone number is unique for that login method, for each of the new tenants. Once associated with multiple tenants, that user can log in to each of the tenants they have access to.

For example, if a user signs up with email password login in the public tenant with email user@example.com, they can join another tenant (t1 for example). This is possible as long as t1 does not already have an email password user with the same email (that is user@example.com).

To associate a user with a tenant, you can call the following API:

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

Remove a user from a 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 remain in the system. However, they cannot log in to any tenant. To remove a user from a tenant, call the following API:

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
}
}
important
  • Users can only share access 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.

See also