Skip to main content

Implementing account deduplication

The approach to implementing account deduplication is to override the backend functions / APIs which create a user such that:

  • We check if a user already exists with that email.
  • If a user does not exist, we call the original implementation; Else
  • We return a message to the frontend telling the user why sign up was rejected.

Add the following override logic to ThirdParty.init on the backend

import ThirdParty from "supertokens-node/recipe/thirdparty";
ThirdParty.init({    signInAndUpFeature: {        providers: [/* ... */]    },    override: {        functions: (originalImplementation) => {            return {                ...originalImplementation,                signInUp: async function (input) {                    let existingUsers = await ThirdParty.getUsersByEmail(input.email);                    if (existingUsers.length === 0) {                        // this means this email is new so we allow sign up                        return originalImplementation.signInUp(input);                    }                    if (existingUsers.find(i => i.thirdParty.id === input.thirdPartyId && i.thirdParty.userId === input.thirdPartyUserId)) {                        // this means we are trying to sign in with the same social login. So we allow it                        return originalImplementation.signInUp(input);                    }                    // this means that the email already exists with another social login method.                    // so we throw an error.                    throw new Error("Cannot sign up as email already exists");                }            }        },        apis: (originalImplementation) => {            return {                ...originalImplementation,                signInUpPOST: async function (input) {                    try {                        return await originalImplementation.signInUpPOST!(input);                    } catch (err: any) {                        if (err.message === "Cannot sign up as email already exists") {                            // this error was thrown from our function override above.                            // so we send a useful message to the user                            return {                                status: "GENERAL_ERROR",                                message: "Seems like you already have an account with another social login provider. Please use that instead."                            }                        }                        throw err;                    }                }            }        }    }})

In the above code snippet, we override the signInUpPOST API as well as the signInUp recipe function. The API is called by the frontend after the user is redirected back to your app from the third party provider's login page. The API then exchanges the auth code with the provider and calls the signInUp function with the user's email and thirdParty info.

We override the signInUp recipe function to:

  • Get all ThirdParty users that have the same input email.
  • If no users exist with that email, it means that this is a new email and so we call the originalImplementation function which creates a new user.
  • If instead, a user exists, but has the same thirdPartyId and thirdPartyUserId, implying that this is a sign in (for example a user who had signed up with Google is signing in with Google), we again allow the operation by calling the originalImplementation function.
  • If neither of the conditions above match, it means that the user is trying to sign up with a third party provider whilst they already have an account with another provider. So here we throw an error with some custom message.

Finally, we override the signInUpPOST API to catch that custom error and return a general error status to the frontend with a message that will be displayed to the user in the sign in form.

Which frontend SDK do you use?
supertokens-web-js / mobile
supertokens-auth-react