Assigning custom user IDs
This feature allows you to change the default SuperTokens user ID (UUID v4) to a value that you prefer. There are two primary use cases for this:
- Use case 1: During migration of users
- Use case 2: If you prefer a different user ID format than the default one.
The way this feature works is that SuperTokens will store and manage the user ID mapping in the SuperTokens core. So when that users logs in, you will get back the mapped (the custom) user ID instead of the SuperTokens user ID.
Features like user roles, user metadata, session will also all work based on the custom user ID.
#
Use case 1: Migration of usersThis topic is better covered under the migration section. The main purpose of this is that you can retain the existing user IDs of your users when migrating them to SuperTokens. This makes it easier to migrate users without having to update the user IDs in your database.
#
Use case 2: If you prefer a different user ID format than the default one.You can call the user ID mapping function post sign up as shown below. It is important to know that user ID mapping can only be done, before there is any other data (session, roles etc) associated with the user.
- NodeJS
- GoLang
- Python
- Other Frameworks
Important
import SuperTokens from "supertokens-node";
import ThirdParty from "supertokens-node/recipe/thirdparty"
import EmailPassword from "supertokens-node/recipe/emailpassword"
import Session from "supertokens-node/recipe/session";
import { RecipeUserId } from "supertokens-node";
SuperTokens.init({
appInfo: {
apiDomain: "...",
appName: "...",
websiteDomain: "..."
},
supertokens: {
connectionURI: "...",
},
recipeList: [
EmailPassword.init({
override: {
functions: (originalImplementation) => {
return {
...originalImplementation,
// override the email password sign up function
signUp: async function (input) {
let response = await originalImplementation.signUp(input);
if (response.status === "OK" && response.user.loginMethods.length === 1 && input.session === undefined) {
let externalUserId = "<CUSTOM USER ID>"
await SuperTokens.createUserIdMapping({ superTokensUserId: response.user.id, externalUserId })
// we modify the response object to have the custom user ID.
response.user.id = externalUserId
response.user.loginMethods[0].recipeUserId = new RecipeUserId(externalUserId);
response.recipeUserId = new RecipeUserId(externalUserId);
}
return response;
},
}
}
}
}),
ThirdParty.init({
override: {
functions: (originalImplementation) => {
return {
...originalImplementation,
// override the thirdparty sign in / up function
signInUp: async function (input) {
let response = await originalImplementation.signInUp(input);
if (response.status === "OK") {
if (response.createdNewRecipeUser && response.user.loginMethods.length === 1 && input.session === undefined) {
let externalUserId = "<CUSTOM USER ID>"
await SuperTokens.createUserIdMapping({ superTokensUserId: response.user.id, externalUserId })
// we modify the response object to have the custom user ID.
response.user.id = externalUserId
response.user.loginMethods[0].recipeUserId = new RecipeUserId(externalUserId);
response.recipeUserId = new RecipeUserId(externalUserId);
}
}
return response;
}
}
}
}
}),
Session.init({ /* ... */ })
]
});
import (
"github.com/supertokens/supertokens-golang/recipe/emailpassword"
"github.com/supertokens/supertokens-golang/recipe/emailpassword/epmodels"
"github.com/supertokens/supertokens-golang/recipe/thirdparty"
"github.com/supertokens/supertokens-golang/recipe/thirdparty/tpmodels"
"github.com/supertokens/supertokens-golang/supertokens"
)
func main() {
supertokens.Init(supertokens.TypeInput{
RecipeList: []supertokens.Recipe{
emailpassword.Init(&epmodels.TypeInput{
Override: &epmodels.OverrideStruct{
Functions: func(originalImplementation epmodels.RecipeInterface) epmodels.RecipeInterface {
// create a copy of the originalImplementation
originalEmailPasswordSignUp := *originalImplementation.SignUp
// override the email password sign up function
(*originalImplementation.SignUp) = func(email, password string, tenantId string, userContext supertokens.UserContext) (epmodels.SignUpResponse, error) {
resp, err := originalEmailPasswordSignUp(email, password, tenantId, userContext)
if err != nil {
return epmodels.SignUpResponse{}, err
}
if resp.OK != nil {
externalUserId := "<CUSTOM USER ID>"
_, err := supertokens.CreateUserIdMapping(resp.OK.User.ID, externalUserId, nil, nil)
if err != nil {
return epmodels.SignUpResponse{}, err
}
resp.OK.User.ID = externalUserId
}
return resp, err
}
return originalImplementation
},
},
}),
thirdparty.Init(&tpmodels.TypeInput{
Override: &tpmodels.OverrideStruct{
Functions: func(originalImplementation tpmodels.RecipeInterface) tpmodels.RecipeInterface {
// create a copy of the originalImplementation
originalThirdPartySignInUp := *originalImplementation.SignInUp
// override the thirdparty sign in / up function
(*originalImplementation.SignInUp) = func(thirdPartyID, thirdPartyUserID, email string, oAuthTokens tpmodels.TypeOAuthTokens, rawUserInfoFromProvider tpmodels.TypeRawUserInfoFromProvider, tenantId string, userContext supertokens.UserContext) (tpmodels.SignInUpResponse, error) {
resp, err := originalThirdPartySignInUp(thirdPartyID, thirdPartyUserID, email, oAuthTokens, rawUserInfoFromProvider, tenantId, userContext)
if err != nil {
return tpmodels.SignInUpResponse{}, err
}
if resp.OK != nil {
if resp.OK.CreatedNewUser {
externalUserId := "<CUSTOM USER ID>"
_, err := supertokens.CreateUserIdMapping(resp.OK.User.ID, externalUserId, nil, nil)
if err != nil {
return tpmodels.SignInUpResponse{}, err
}
resp.OK.User.ID = externalUserId
}
}
return resp, err
}
return originalImplementation
},
},
}),
},
})
}
from supertokens_python import init, InputAppInfo
from supertokens_python.asyncio import create_user_id_mapping
from supertokens_python.recipe import thirdparty, emailpassword, session
from supertokens_python.recipe.thirdparty.interfaces import (
RecipeInterface as ThirdPartyRecipeInterface,
SignInUpOkResult,
)
from supertokens_python.recipe.emailpassword.interfaces import (
RecipeInterface as EmailPasswordRecipeInterface,
SignUpOkResult,
)
from supertokens_python.recipe.thirdparty.types import RawUserInfoFromProvider
from typing import Dict, Any, Optional, Union
from supertokens_python.recipe.session.interfaces import SessionContainer
from supertokens_python.types import RecipeUserId
def override_thirdparty_functions(
original_implementation: ThirdPartyRecipeInterface,
) -> ThirdPartyRecipeInterface:
original_thirdparty_sign_in_up = original_implementation.sign_in_up
async def thirdparty_sign_in_up(
third_party_id: str,
third_party_user_id: str,
email: str,
is_verified: bool,
oauth_tokens: Dict[str, Any],
raw_user_info_from_provider: RawUserInfoFromProvider,
session: Optional[SessionContainer],
should_try_linking_with_session_user: Union[bool, None],
tenant_id: str,
user_context: Dict[str, Any],
):
result = await original_thirdparty_sign_in_up(
third_party_id,
third_party_user_id,
email,
is_verified,
oauth_tokens,
raw_user_info_from_provider,
session,
should_try_linking_with_session_user,
tenant_id,
user_context,
)
if isinstance(result, SignInUpOkResult):
# user object contains the ID and email of the user
user = result.user
print(user)
# This is the response from the OAuth 2 provider that contains their tokens or user info.
provider_access_token = result.oauth_tokens["access_token"]
print(provider_access_token)
if result.raw_user_info_from_provider.from_user_info_api is not None:
first_name = result.raw_user_info_from_provider.from_user_info_api[
"first_name"
]
print(first_name)
if (
result.created_new_recipe_user
and len(result.user.login_methods) == 1
and session is None
):
external_user_id = "<CUSTOM USER ID>"
await create_user_id_mapping(result.user.id, external_user_id)
result.user.id = external_user_id
result.user.login_methods[0].recipe_user_id = RecipeUserId(
external_user_id
)
result.recipe_user_id = RecipeUserId(external_user_id)
return result
original_implementation.sign_in_up = thirdparty_sign_in_up
return original_implementation
def override_emailpassword_functions(
original_implementation: EmailPasswordRecipeInterface,
) -> EmailPasswordRecipeInterface:
original_emailpassword_sign_up = original_implementation.sign_up
async def emailpassword_sign_up(
email: str,
password: str,
tenant_id: str,
session: Union[SessionContainer, None],
should_try_linking_with_session_user: Union[bool, None],
user_context: Dict[str, Any],
):
result = await original_emailpassword_sign_up(
email,
password,
tenant_id,
session,
should_try_linking_with_session_user,
user_context,
)
if (
isinstance(result, SignUpOkResult)
and len(result.user.login_methods) == 1
and session is None
):
external_user_id = "<CUSTOM USER ID>"
await create_user_id_mapping(result.user.id, external_user_id)
result.user.id = external_user_id
result.user.login_methods[0].recipe_user_id = RecipeUserId(external_user_id)
result.recipe_user_id = RecipeUserId(external_user_id)
return result
original_implementation.sign_up = emailpassword_sign_up
return original_implementation
init(
app_info=InputAppInfo(api_domain="...", app_name="...", website_domain="..."),
framework="...",
recipe_list=[
thirdparty.init(
override=thirdparty.InputOverrideConfig(
functions=override_thirdparty_functions
),
),
emailpassword.init(
override=emailpassword.InputOverrideConfig(
functions=override_emailpassword_functions
),
),
session.init(),
],
)