Modifications to Login
In this section we will be modifying login to enable the migrating of users with Email Password or Social accounts from Auth0 to SuperTokens.
info
The following code is implemented for Nodejs, but, you can apply the same logic in your own tech stack.
- Email Password Account Migration
- Social Account Migration
#
Email Password Account Migration#
Flowtip
If you have not stored your user's Auth0 userId in your database you can ignore the steps mentioned in Box 5 and Box 8 of the flow diagram.

#
ImplementationWe will be overriding SuperToken's login functions to enable account migration, if a user signs up, a new SuperTokens account will be created so no changes need to be made to the sign up functionality.
signIn
function on the backend:#
Modifying the import ThirdPartyEmailPassword from "supertokens-node/recipe/thirdpartyemailpassword";
ThirdPartyEmailPassword.init({ override: { functions: (originalImpl) => { return { ...originalImpl, // EmailPassword Login function modification emailPasswordSignIn: async function (input) {
// Box 2: check if the User exists in Auth0 if (await doesUserExistInAuth0(input.email)) {
// Box 3: check if user exists in SuperTokens let superTokensUsers = await this.getUsersByEmail({ email: input.email, userContext: input.userContext }); let emailPasswordUser = undefined;
for (let i = 0; i < superTokensUsers.length; i++) { // if the thirdParty field in the user object is undefined, then the user is an EmailPassword account. if (superTokensUsers[i].thirdParty === undefined) { emailPasswordUser = superTokensUsers[i] break; } }
let response;
if (emailPasswordUser === undefined) { // EmailPassword user does not exist in SuperTokens
// Box 6: validate users credentials in Auth0 let auth0UserData = await validateAndGetUserInfoFromAuth0(input.email, input.password)
if (auth0UserData === undefined) { // Box 9: credentials are incorrect return { status: "WRONG_CREDENTIALS_ERROR" } }
// Box 7: call the signup function to create a new SuperTokens user. response = await this.emailPasswordSignUp(input)
if (response.status !== "OK") { return { status: "WRONG_CREDENTIALS_ERROR", }; }
// Box 8: map the Auth0 userId to the SuperTokens userId, to learn more about user mapping please check the User Id Mapping section. // If you have not stored the users Auth0 userId in your tables, you can ignore this step await setUserIdMapping({ auth0UserId: auth0UserData.sub, supertokensUserId: response.user.id })
// Box 5: Set the userId in the response to use the Auth0 userId, to learn more about user mapping please check the User Id Mapping section. // If you have not stored the users Auth0 userId in your tables, you can ignore this step response.user.id = auth0UserData.sub
} else { // Box 4: EmailPassword User with the input credentials exists in SuperTokens, so call the original signIn implementation response = await originalImpl.emailPasswordSignIn(input)
if (response.status !== "OK") { return response; }
// Box 5: Set the userId in the response to use the Auth0 userId, to learn more about user mapping please check the User Id Mapping section. // If you have not stored the users Auth0 userId in your tables, you can ignore this step response.user.id = (await getUserIdMapping({ supertokensUserId: emailPasswordUser.id })).auth0UserId } return response
} else { // Box 10: user does not exist in Auth0 call original signIn implementation return originalImpl.emailPasswordSignIn(input) } }, } } },})
#
Validating Auth0 credentialsIn order to make the changes mentioned above, you have to now decide how you would like to validate the user's Auth0 account credentials
There are two methods of validating user credentials:
- Using Auth0's APIs
- Using the exported user data and password hashes from Auth0
- Using Auth0's APIs
- Using the exported Auth0 user data and password hashes.
#
Using Auth0's APIsWe will be using Auth0's APIs to validate user credentials. Please refer to Auth0's documentation to get the complete spec for the APIs used below.
#
Prerequisites- This method assumes you have access to your Auth0 account and database for the complete duration of user migration.
- To enable email and password validation, you will need to add the database connection name to the
Default Directory
field in your Auth0 tenant settings.
For example:
- The default connection name for the database is
Username-Password-Authentication
, you can find your database connections under Authentication -> Database on your Auth0 dashboard.

- Set your database connection name in your
Default Directory
field in your tennant settings.

#
ImplementationImplementation for the validateAndGetUserInfoFromAuth0
and doesUserExistInAuth0
functions used in SuperTokens signIn
function:
import axios from "axios";
// Box 6: Validates user credentials by generating an access token with their credentials and returns user datalet validateAndGetUserInfoFromAuth0 = async (email: string, password: string) => {
let access_token; try { // generate an user access token using the input credentials access_token = (await axios({ method: "post", // TODO: add your Auth0 domain url: "https://YOUR_AUTH0_DOMAIN/oauth/token", headers: { 'Accept': 'application/json', 'Content-Type': 'application/json', }, data: { // TODO: add your Auth0 app's clientId client_id: "YOUR_CLIENT_ID", grant_type: "password", username: email, password: password, } })).data.access_token;
} catch (error) { // input credentials are invalid return undefined }
// retrieve the user's data using the access token if valid credentials were passed // TODO: add your Auth0 domain let userResponse = await axios.get("https://YOUR_AUTH0_DOMAIN/userInfo", { headers: { "authorization": `Bearer ${access_token}` }, }) return userResponse.data}
// Box 2: Checks if a user with an EmailPassword account exists in Auth0 with the input emaillet doesUserExistInAuth0 = async (email: string) => {
// generate an access token to use the Auth0's Management API. let access_token = (await axios({ method: 'POST', // TODO: add your Auth0 domain url: "https://YOUR_DOMAIN/oauth/token", headers: { 'content-type': 'application/json' }, data: { // TODO: add your Auth0 app's clientId client_id: "CLIENT_ID", // TODO: add your Auth0 app's clientSecret client_secret: "CLIENT_SECRET", // TODO: add your Auth0 domain audience: "https://YOUR_DOMAIN/api/v2/", grant_type: "client_credentials" } })).data.access_token
// check if a user exists with the input email and is not a Social Account let response = await axios({ method: 'GET', // TODO: add your Auth0 domain url: "https://YOUR_DOMAIN/api/v2/users", params: { q: `identities.isSocial:false AND email:${email}` }, headers: { authorization: `Bearer ${access_token}` } }) if (response.data[0] !== undefined) { return true } return false}
#
Using the exported Auth0 user data and password hashes.#
Prerequisites- This method assumes you have an Auth0 enterprise account so you can export your Auth0 user data and password hashes.
- You can refer to the following guides on how to export your user data and password hashes.
#
ImplementationThe Implementation below follows the following sample data structure.
{"user_id":"auth0|60425da93519d90068f82966","email":"[email protected]","identities": [{"user_id": "60425da93519d90068f82966","provider": "auth0","connection": "Username-Password-Authentication","isSocial": false}],"name":"[email protected]","nickname":"test","created_at":"2021-03-05T16:34:49.518Z","updated_at":"2021-03-05T16:34:49.518Z","email_verified":false}
{"_id":{"$oid":"60425dc43519d90068f82973"},"email_verified":false,"email":"[email protected]","passwordHash":"$2b$10$Z6hUTEEeoJXN5/AmSm/4.eZ75RYgFVriQM9LPhNEC7kbAbS/VAaJ2","password_set_date":{"$date":"2021-03-05T16:35:16.775Z"},"tenant":"dev-rwsbs6ym","connection":"Username-Password-Authentication","_tmp_is_unique":true}
Implementation for the validateAndGetUserInfoFromAuth0
and doesUserExistInAuth0
functions used in SuperTokens signIn
function:
// Auth0 uses bcrypt to hash user passwords, we can use the bcrypt library to validate the input password.import bcrypt from "bcryptjs"import fs from "fs";
// Since password hashes are mapped to user data through userIds, you will need to load both files.
// load your exported Auth0 user data// TODO: Add your user data file namelet auth0UsersDataString = fs.readFileSync(__dirname + "YOUR_USER_DATA")let auth0UsersData = JSON.parse(auth0UsersDataString.toString());
// load your exported Auth0 password hashes// TODO: Add your password hashes file namelet auth0PasswordHashesString = fs.readFileSync(__dirname + "YOUR_PASSWORD_HASHES")let auth0PasswordHashes = JSON.parse(auth0PasswordHashesString.toString());
// Box 6: Validate user credentials and retrieve user datalet validateAndGetUserInfoFromAuth0 = async (email: string, password: string) => {
for (let i = 0; i < auth0UsersData.length; i++) { // check if a user with the input email exists and they are not a Social Account if ((!auth0UsersData[i].identities[0].isSocial) && auth0UsersData[i].email === email) {
let usersPasswordHash = undefined // retrieve the password hash from the passwordHash data using the the associated userId. for (let i = 0; i < auth0PasswordHashes.length; i++) { // check if the passwordHash userId matches the users userId if (auth0PasswordHashes[i]._id.$oid === auth0UsersData[i].identities[0].user_id) { usersPasswordHash = auth0PasswordHashes[i].passwordHash; break; } }
if (usersPasswordHash === undefined) { return undefined }
// validate the password hash with the users password if (bcrypt.compareSync(password, usersPasswordHash)) { return auth0UsersData[i] } else { return undefined } } }}
// Box 2: check that the user exists in Auth0let doesUserExistInAuth0 = async (email: string) => {
// check if input email has an associated user for (let i = 0; i < auth0UsersData.length; i++) { // check that a user with the input email exists and they are not a Social account. if ((!auth0UsersData[i].identities[0].isSocial) && auth0UsersData[i].email === email) { return true } } return false}
#
Social Account Migration#
Flowtip
If you have not stored your user's Auth0 userId in your database you can ignore the steps mentioned in Box 6 and Box 8 of the flow diagram.

#
ImplementationWe will be overriding SuperToken's login functions to enable account migration, if a user signs up, a new SuperTokens account will be created so no changes need to be made to the sign up functionality.
signInUp
function on the backend:#
Modifying the import ThirdPartyEmailPassword from "supertokens-node/recipe/thirdpartyemailpassword";
ThirdPartyEmailPassword.init({ override: { functions: (originalImpl) => { return { ...originalImpl, // Social Login function modification thirdPartySignInUp: async function (input) {
// Box 2: Get userInfo from Auth0 with Social Provider id. let auth0UserInfo = await getThirdPartyUserFromAuth0(input.user.thirdParty.userId)
// Box 3: check if userInfo exists if (auth0UserInfo !== undefined) {
// Box 4: call the Supertokens signInUp implementation let response = await originalImpl.thirdPartySignInUp(input)
if (response.status !== "OK") { return response; }
// Box 5: check if a new SuperTokens user is created if (response.createdNewUser) {
// Box 6: map the Auth0 userId to the SuperTokens userId, to learn more about user mapping please check the User Id Mapping section. // If you have not stored the users Auth0 userId in your tables, you can ignore this step await setUserIdMapping({ auth0UserId: auth0UserInfo.user_id, supertokensUserId: response.user.id })
// Box 7: Set the newly created flag value to false in the response response.createdNewUser = false }
// Box 8: Set the userId in the response to use the Auth0 userId, to learn more about user mapping please check the User Id Mapping section. // If you have not stored the users Auth0 userId, you can ignore this step response.user.id = auth0UserInfo.user_id
return response
} else { // Box 9: Auth0 user does not exist return await originalImpl.thirdPartySignInUp(input) } } } } },})
getThirdPartyUserFromAuth0
function#
Implementing the In order to make the changes mentioned above, you have to decide how you would like to retrieve your user's data.
There are two methods :
- Using Auth0's APIs
- Using the exported user data from Auth0
- Using Auth0's APIs
- Using the exported Auth0 user data
#
Using Auth0's APIsIn this method we will be using Auth0's APIs to check if the user signing in is an Auth0 user.
#
Prerequisites- This method assumes you have access to your Auth0 account and database for the complete duration of user migration.
#
ImplementationImplementation for the getThirdPartyUserFromAuth0
function used in SuperTokens signInUp
function:
import axios from "axios";
// generate an access_token so you can use the Auth0 management APIlet generateAccessToken = async () => { let response = (await axios({ method: 'POST', // TODO: add your Auth0 app domain url: "https://YOUR_DOMAIN/oauth/token", headers: { 'content-type': 'application/json' }, data: { // TODO: add your Auth0 app client id and secret client_id: "CLIENT_ID", client_secret: "CLIENT_SECRET", // TODO: add you Auth0 app domain audience: "https://YOUR_DOMAIN/api/v2/", grant_type: "client_credentials" } }))
return response.data.access_token}
let access_token = generateAccessToken()
// Get the social account user info from Auth0 with the input thirdPartyId.let getThirdPartyUserFromAuth0 = async (thirdPartyId: string) => {
// send a request to Auth0's get user API with the input thirdPartyId as the search criteria. let response = await axios({ method: 'GET', // TODO: add your Auth0 app domain url: "https://YOUR_DOMAIN/api/v2/users", params: { q: `identities.user_id:"${thirdPartyId}"` }, headers: { authorization: `Bearer ${access_token}` } })
// check if user information exists in response. if (response.data[0] !== undefined) { // return the user's Auth0 userId return response.data[0]; }
return undefined}
#
Using the exported Auth0 user data.The Implementation below follows the following sample data structure. Please refer to Auth0's docs on how to export user data.
{"user_id":"google|60425da93519d90068f82966","email":"[email protected]","identities": [{"user_id": "60425da93519d90068f82966","provider": "google","connection": "Username-Password-Authentication","isSocial": true}],"name":"[email protected]","nickname":"test","created_at":"2021-03-05T16:34:49.518Z","updated_at":"2021-03-05T16:34:49.518Z","email_verified":false}
#
ImplementationImplementation for the getThirdPartyUserFromAuth0
function used in SuperTokens signInUp
function:
import fs from "fs";
// load your exported Auth0 user data// TODO: Add your user data file namelet auth0UsersDataString = fs.readFileSync(__dirname + "YOUR_USER_DATA.json")let auth0UsersData = JSON.parse(auth0UsersDataString.toString());
let getThirdPartyUserFromAuth0 = async (thirdPartyId: string) => {
for (let i = 0; i < auth0UsersData.length; i++) { // check if current user has a social account and the Social Provider Id is a match to the thirdPartyId. if ((auth0UsersData[i].identities[0].isSocial) && auth0UsersData[i].identities[0].user_id === thirdPartyId) { // return Auth0 user data return auth0UsersData[0] } } return undefined}
We can now move on to the next section discussing the setUserIdMapping
and getUserIdMapping
functions and how userId's should be mapped.