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.
Adding a custom third party provider
Whilst there are several built-in providers that SuperTokens offers, you may want to add your own provider. This page will show you how to do that on a per tenant basis so that you can add a custom enterprise connection or a social connection to each tenant.
Once you have created a tenant, you want to call the API / function to create a new provider for the tenant as shown below:
#
Via OAuth endpoints- NodeJS
- GoLang
- Python
- cURL
Important
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
}
}
import (
"github.com/supertokens/supertokens-golang/recipe/multitenancy"
"github.com/supertokens/supertokens-golang/recipe/thirdparty/tpmodels"
)
func main() {
tenantId := "..."
resp, err := multitenancy.CreateOrUpdateThirdPartyConfig(tenantId, tpmodels.ProviderConfig{
ThirdPartyId: "custom",
Name: "Custom Provider",
Clients: []tpmodels.ProviderClientConfig{
{
ClientID: "...",
ClientSecret: "...",
Scope: []string{"email", "profile"},
},
},
AuthorizationEndpoint: "https://example.com/oauth/authorize",
AuthorizationEndpointQueryParams: map[string]interface{}{ // optional
"someKey1": "value1",
"someKey2": nil,
},
TokenEndpoint: "https://example.com/oauth/token",
TokenEndpointBodyParams: map[string]interface{}{ // optional
"someKey1": "value1",
},
UserInfoEndpoint: "https://example.com/oauth/userinfo",
UserInfoMap: tpmodels.TypeUserInfoMap{
FromUserInfoAPI: struct{UserId string "json:\"userId,omitempty\""; Email string "json:\"email,omitempty\""; EmailVerified string "json:\"emailVerified,omitempty\""} {
UserId: "id",
Email: "email",
EmailVerified: "email_verified",
},
},
}, nil)
if err != nil {
// handle error
}
if resp.OK.CreatedNew {
// Custom provider added to tenant
} else {
// Existing custom provider config overwritten for tenant
}
}
- Asyncio
- Syncio
from supertokens_python.recipe.multitenancy.asyncio import create_or_update_third_party_config
from supertokens_python.recipe.thirdparty.provider import ProviderConfig, ProviderClientConfig, UserInfoMap, UserFields
async def some_func():
tenant_id = "..."
result = await create_or_update_third_party_config(tenant_id, ProviderConfig(
third_party_id="custom",
name="Custom Provider",
clients=[
ProviderClientConfig(
client_id="...",
client_secret="...",
scope=["email", "profile"],
),
],
authorization_endpoint="https://example.com/oauth/authorize",
authorization_endpoint_query_params={
"someKey1": "value1",
"someKey2": None,
},
token_endpoint="https://example.com/oauth/token",
token_endpoint_body_params={
"someKey1": "value1",
},
user_info_endpoint="https://example.com/oauth/userinfo",
user_info_map=UserInfoMap(
from_user_info_api=UserFields(
user_id="id",
email="email",
email_verified="email_verified",
),
from_id_token_payload=UserFields(),
),
))
if result.status != "OK":
print("handle error")
elif result.created_new:
print("Custom provider added to tenant")
else:
print("Existing custom provider config overwritten for tenant")
from supertokens_python.recipe.multitenancy.syncio import create_or_update_third_party_config
from supertokens_python.recipe.thirdparty.provider import ProviderConfig, ProviderClientConfig, UserInfoMap, UserFields
tenant_id = "..."
result = create_or_update_third_party_config(tenant_id, ProviderConfig(
third_party_id="custom",
name="Custom Provider",
clients=[
ProviderClientConfig(
client_id="...",
client_secret="...",
scope=["email", "profile"],
),
],
authorization_endpoint="https://example.com/oauth/authorize",
authorization_endpoint_query_params={
"someKey1": "value1",
"someKey2": None,
},
token_endpoint="https://example.com/oauth/token",
token_endpoint_body_params={
"someKey1": "value1",
},
user_info_endpoint="https://example.com/oauth/userinfo",
user_info_map=UserInfoMap(
from_user_info_api=UserFields(
user_id="id",
email="email",
email_verified="email_verified",
),
from_id_token_payload=UserFields(),
),
))
if result.status != "OK":
print("handle error")
elif result.created_new:
print("Custom provider added to tenant")
else:
print("Existing custom provider config overwritten for tenant")
- 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": "custom",
"name": "Custom provider",
"clients": [{
"clientId": "...",
"clientSecret": "...",
"scope": ["email", "profile"],
}],
"authorizationEndpoint": "https://example.com/oauth/authorize",
"authorizationEndpointQueryParams": {
"someKey1": "value1",
"someKey2": "value2"
},
"tokenEndpoint": "https://example.com/oauth/token",
"tokenEndpointBodyParams": {
"someKey1": "value1",
},
"userInfoEndpoint": "https://example.com/oauth/userinfo",
"userInfoMap": {
"fromUserInfoAPI": {
"userId": "id",
"email": "email",
"emailVerified": "email_verified"
}
}
}
}'
curl --location --request PUT '/<TENANT_ID>/recipe/multitenancy/config/thirdparty' \
--header 'api-key: ' \
--header 'Content-Type: application/json' \
--data-raw '{
"config": {
"thirdPartyId": "custom",
"name": "Custom provider",
"clients": [{
"clientId": "...",
"clientSecret": "...",
"scope": ["email", "profile"],
}],
"authorizationEndpoint": "https://example.com/oauth/authorize",
"authorizationEndpointQueryParams": {
"someKey1": "value1",
"someKey2": "value2"
},
"tokenEndpoint": "https://example.com/oauth/token",
"tokenEndpointBodyParams": {
"someKey1": "value1",
},
"userInfoEndpoint": "https://example.com/oauth/userinfo",
"userInfoMap": {
"fromUserInfoAPI": {
"userId": "id",
"email": "email",
"emailVerified": "email_verified"
}
}
}
}'
You can see all the options here.
The
tenantId
is a unique ID that identifies the tenant for whom you want to add the custom provider. If not specified, it will add it for the"public"
tenantId (which is the default one).
The
thirdPartyId
is a unique ID for this provider which helps SuperTokens identify the provider. For example, thethirdPartyId
for the built in Google provider is"google"
.The
name
field is sent to the frontend and can be used to render the login button on the frontend UI. For example, if you set thename
to"XYZ"
, the pre built UI on the frontend will display the text"Login using XYZ"
on the button for this provider.The
clients
array represents the client credentials / settings for each of your frontend clients. Most of the time, one client item is enough, but if the provider requires to use different client ID / secret for web vs mobile apps, you would need to add two items to this array - one for web, and one for mobile. In this case, you would also need to add theclientType: string
config for each of the items in theclients
array.AuthorizationEndpoint
corresponds to the URL to which the user should be redirected to for the login. For example for Google, this is"https://accounts.google.com/o/oauth2/v2/auth"
.AuthorizationEndpointQueryParams
is an optional config, but it can be used to modify the query params added to theAuthorizationEndpoint
. You can use this config to add new keys, or to modify or remove (by setting to null) query params added by SuperTokens.
TokenEndpoint
corresponds to the API that is used to exchange Authorization Code for the user's Access Token or the ID Token. For example for Google, the value of this is"https://oauth2.googleapis.com/token"
.TokenEndpointBodyParams
is an optional config, but it can be used to modify the request body sent to theTokenEndpoint
. You can use this config to add new keys, or to modify or remove (by setting to null) body params added by SuperTokens.
UserInfoEndpoint
corresponds to the API that provides user information using the AccessToken. For Google, the value of this is"https://www.googleapis.com/oauth2/v1/userinfo"
. Once this endpoint is called, SuperTokens fetches user info (like their userID and email ID) from the JSON response based on the config forUserInfoMap.FromUserInfoAPI
map. For example, the value of the map for Google is:{
userId: "id",
email: "email",
emailVerified: "email_verified"
}This means that when SuperTokens gets back the JSON from the
/userinfo
endpoint, it will read thejsonResponse.id
field to get the user's Google ID, thejsonResponse.email
field to get back the user's email and thejsonResponse.email_verified
field to know if the user's email is verified or not.If you want to fetch a value from a nested JSON object, you can specify something like
userId: "user.id"
, in which case the provider's user ID will be fetched usingjsonResponse.user.id
.
#
Via Open ID connect (OIDC) endpointsIf the provider is Open ID Connect (OIDC) compatible, you can provide url for the OIDCDiscoverEndpoint
config. The backend SDK will automatically discover authorization endpoint, token endpoint and the user info endpoint by querying the <OIDCDiscoverEndpoint>/.well-known/openid-configuration
.
Below is an example of how to set the OIDC discovery endpoint:
- NodeJS
- GoLang
- Python
- cURL
Important
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",
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
}
}
import (
"github.com/supertokens/supertokens-golang/recipe/multitenancy"
"github.com/supertokens/supertokens-golang/recipe/thirdparty/tpmodels"
)
func main() {
tenantId := "..."
resp, err := multitenancy.CreateOrUpdateThirdPartyConfig(tenantId, tpmodels.ProviderConfig{
ThirdPartyId: "custom",
Name: "Custom provider",
Clients: []tpmodels.ProviderClientConfig{
{
ClientID: "...",
ClientSecret: "...",
Scope: []string{"profile", "email"},
},
},
OIDCDiscoveryEndpoint: "https://example.com",
AuthorizationEndpointQueryParams: map[string]interface{}{ // optional
"someKey1": "value1",
"someKey2": nil,
},
UserInfoMap: tpmodels.TypeUserInfoMap{
FromIdTokenPayload: struct{UserId string "json:\"userId,omitempty\""; Email string "json:\"email,omitempty\""; EmailVerified string "json:\"emailVerified,omitempty\""} {
UserId: "id",
Email: "email",
EmailVerified: "email_verified",
},
},
}, nil)
if err != nil {
// handle error
}
if resp.OK.CreatedNew {
// Custom provider added to tenant
} else {
// Existing custom provider config overwritten for tenant
}
}
from supertokens_python.recipe.multitenancy.asyncio import create_or_update_third_party_config
from supertokens_python.recipe.thirdparty.provider import ProviderConfig, ProviderClientConfig, UserInfoMap, UserFields
async def some_func():
tenant_id = "..."
result = await create_or_update_third_party_config(tenant_id, ProviderConfig(
third_party_id="custom",
name="Custom Provider",
clients=[
ProviderClientConfig(
client_id="...",
client_secret="...",
scope=["email", "profile"],
),
],
oidc_discovery_endpoint="https://example.com",
authorization_endpoint_query_params={
"someKey1": "value1",
"someKey2": None,
},
user_info_map=UserInfoMap(
from_user_info_api=UserFields(),
from_id_token_payload=UserFields(
user_id="id",
email="email",
email_verified="email_verified",
),
),
))
if result.status != "OK":
print("handle error")
elif result.created_new:
print("Custom provider added to tenant")
else:
print("Existing custom provider config overwritten for tenant")
- 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": "custom",
"name": "Custom provider",
"clients": [{
"clientId": "...",
"clientSecret": "...",
"scope": ["email", "profile"],
}],
"oidcDiscoveryEndpoint": "https://example.com",
"authorizationEndpointQueryParams": {
"someKey1": "value1",
"someKey2": "value2"
},
"userInfoMap": {
"fromIdTokenPayload": {
"userId": "id",
"email": "email",
"emailVerified": "email_verified"
}
}
}
}'
curl --location --request PUT '/<TENANT_ID>/recipe/multitenancy/config/thirdparty' \
--header 'api-key: ' \
--header 'Content-Type: application/json' \
--data-raw '{
"config": {
"thirdPartyId": "custom",
"name": "Custom provider",
"clients": [{
"clientId": "...",
"clientSecret": "...",
"scope": ["email", "profile"],
}],
"oidcDiscoveryEndpoint": "https://example.com",
"authorizationEndpointQueryParams": {
"someKey1": "value1",
"someKey2": "value2"
},
"userInfoMap": {
"fromIdTokenPayload": {
"userId": "id",
"email": "email",
"emailVerified": "email_verified"
}
}
}
}'
You can see all the options here.
The
tenantId
is a unique ID that identifies the tenant for whom you want to add the custom provider. If not specified, it will add it for the"public"
tenantId (which is the default one).Notice that
oidcDiscoveryEndpoint
doesn't have the/.well-known/openid-configuration
appended to it. That's because our backend SDK adds this automatically, so you don't need to.
- The config values are similar to the ones in the "Via OAuth endpoints" method. Please read that section to understand the
thirdPartyId
,name
,clients
config. - Unlike the "Via OAuth endpoints", we extract the user's info from the ID token payload using the config specified by you in the
UserInfoMap.FromIdTokenPayload
map. - You can also add the
UserInfoMap.FromUserInfoAPI
map as done in the "Via OAuth endpoints" section. SuperTokens will auto merge the user information.