Skip to main content

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#

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.

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

ThirdPartyEmailPassword.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 superTokensUsers = await this.getUsersByEmail({ email: input.email });                        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 (users[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.signUp(input)

                            // 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.signIn(input)
                            // 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.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.
  • Set your database connection name in your Default Directory field in your tennant settings.
Implementation#

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

Nodejs

// Box 6: Validates user credentials by generating an access token with their credentials and returns user datalet validateAndGetUserInfoFromAuth0 = async (email, password) => {
    let accessToken;    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) => {
    // 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}

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