Skip to main content

Modifications to Login

In this section we will be modifying login to enable the migrating of users with Email Password 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.

Flow#

tip

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.

Flow of login to migrate users with email-password accounts in Auth0 to SuperTokens

Implementation#

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

Modifying the signIn function on the backend:#

Nodejs
import EmailPassword from "supertokens-node/recipe/emailpassword";import fs from 'fs';
// TODO: Add your user data file namelet auth0UsersDataString = fs.readFileSync(__dirname + "YOUR_USER_DATA")let auth0UsersData = JSON.parse(auth0UsersDataString.toString());
async function doesUserExistInAuth0(email: string): Promise<boolean> {    return true;}
let validateAndGetUserInfoFromAuth0 = async (email: string, password: string): Promise<{    user_id: string,    email: string,} | undefined> => {    // ...    return {        user_id: "...",        email: "...",    };}
let setUserIdMapping = async (    input: {        auth0UserId: string,        supertokensUserId: string    }): Promise<void> => {    // set the mapping in your data store}
let getUserIdMapping = async (    input: {        auth0UserId?: string,        supertokensUserId?: string    }): Promise<{ auth0UserId: string, supertokensUserId: string } | undefined> => {    // Refer to user id mapping    return {        auth0UserId: "...",        supertokensUserId: "...",    };}
EmailPassword.init({    override: {        functions: (originalImpl) => {            return {                ...originalImpl,                // EmailPassword Login function modification                signIn: 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 supertokensUser = await this.getUserByEmail({ email: input.email,                        userContext: input.userContext });
                        if (supertokensUser === undefined) {                            // EmailPassword User with the input credentials does not exist in SuperTokens
                            // Box 6: validate users credentials in Auth0                            let auth0UserInfo = await validateAndGetUserInfoFromAuth0(input.email, input.password)
                            if (auth0UserInfo === undefined) {                                // Box 9: credentials are incorrect                                return {                                    status: "WRONG_CREDENTIALS_ERROR"                                }                            }
                            // Box 7: call the signup function to create a new SuperTokens user.                            let response = await this.signUp(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: auth0UsersData.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 = response.user.id                            return response;                        } else {                            // Box 4: EmailPassword User with the input credentials exists in SuperTokens, so call the original signIn implementation                            let response = await originalImpl.signIn(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: supertokensUser.id }))!.auth0UserId                            return response;                        }
                    } else {                        // Box 10: user does not exist in Auth0 call original signIn implementation                        return originalImpl.signIn(input)                    }                },            }        }    },})

Validating Auth0 credentials#

In 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#

We 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.
Database connections in Auth0
  • Set your database connection name in your Default Directory field in your tennant settings.
Updating default directory in Auth0 tenant settings

Implementation#

Implementation for the validateAndGetUserInfoFromAuth0 and doesUserExistInAuth0 functions used in SuperTokens signIn function:

Nodejs
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
    // checks 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}

We can now move on to the next section discussing the setUserIdMapping and getUserIdMapping functions and how userId's should be mapped.