Mapping Social Provider Email to Email in Cognito User Pool

0

Hey everyone, I have following problem: If a user first sign-ups with Email: example@example.com in Cognito and then with a social provider with the same Email: example@example.com, then cognito creates two Accounts, although I would like to have 1 User with the unque Email. Cognito should recognize, that there is already an account and map it to it. I'm using Amplify, Typescript. My resource.ts looks like that:

export const auth = defineAuth({
  loginWith: {
    email: true,
    externalProviders: {
      google: {
        clientId: secret("GOOGLE_CLIENT_ID"),
        clientSecret: secret("GOOGLE_CLIENT_SECRET"),
        attributeMapping: {
          email: "email",
        },
        scopes: ["email", "profile", "openid"],
      },
      facebook: {
        clientId: secret("FACEBOOK_CLIENT_ID"),
        clientSecret: secret("FACEBOOK_CLIENT_SECRET"),
        attributeMapping: {
          email: "email",
        },
        scopes: ["email", "public_profile"],
      },
      logoutUrls: [
        "https://main.xxxx.com",
        "http://localhost:5173/",
      ],
      callbackUrls: [
        "https://main.xxxx.com",
        "http://localhost:5173/",
      ],
    },
  },
  triggers: { postConfirmation },
});

I tried a postConfirmation Lambda, but it doesn't work:

async function IsEmailTwiceInUserpool(userPoolId: string, email: string) {
  const client = new CognitoIdentityProviderClient({});
  const command = new ListUsersCommand({
    UserPoolId: userPoolId,
    Filter: 'email = "${email}"',
  });
  var response = await client.send(command);

  return Array.isArray(response.Users) && response.Users?.length > 1
    ? true
    : false;
}
async function deleteUser(userPoolId: string, userName: string) {
  const client = new CognitoIdentityProviderClient({});
  const deleteCommand = new AdminDeleteUserCommand({
    UserPoolId: userPoolId,
    Username: userName,
  });
  await client.send(deleteCommand);
}

export const handler: PostConfirmationTriggerHandler = async (
  event,
  context
) => {
  const { userPoolId, request, userName } = event;
  const email = request.userAttributes["email"];

  if (await IsEmailTwiceInUserpool(userPoolId, email)) {
    deleteUser(userPoolId, userName);
    throw new Error("user exists");
  }
  return event;
};
1 Answer
0

Hi Thomas,

The issue you're encountering is common when working with Cognito and multiple sign-in methods. Unfortunately, Cognito doesn’t natively merge accounts when a user signs up with the same email via different providers. However, you can work around this using a Pre-Signup Lambda trigger instead of Post-Confirmation.

Here’s a high-level approach:

  1. Pre-Signup Trigger: Create a Lambda function to check if the email exists in the User Pool before allowing the new signup. If it exists, link the social provider to the existing account.

  2. Link Social Provider: In the Lambda, use the AdminLinkProviderForUser API to link the new social login to the existing Cognito user.

Here’s a rough idea:

import {
  CognitoIdentityProviderClient,
  AdminLinkProviderForUserCommand,
  ListUsersCommand,
} from "@aws-sdk/client-cognito-identity-provider";

const client = new CognitoIdentityProviderClient({});

export const handler: PreSignUpTriggerHandler = async (event) => {
  const { userPoolId, request } = event;
  const email = request.userAttributes["email"];

  const listUsersCommand = new ListUsersCommand({
    UserPoolId: userPoolId,
    Filter: `email = "${email}"`,
  });

  const response = await client.send(listUsersCommand);

  if (response.Users?.length > 0) {
    const existingUser = response.Users[0];

    const linkCommand = new AdminLinkProviderForUserCommand({
      UserPoolId: userPoolId,
      DestinationUser: {
        ProviderName: "Cognito",
        ProviderAttributeValue: existingUser.Username,
      },
      SourceUser: {
        ProviderName: event.userName.split("_")[0], // e.g., "Google"
        ProviderAttributeName: "Cognito_Subject",
        ProviderAttributeValue: event.userName.split("_")[1],
      },
    });

    await client.send(linkCommand);
    throw new Error("User linked to existing account.");
  }

  return event;
};
  1. Modify Resource Configuration: Ensure your Cognito User Pool is set to prevent duplicate email sign-ups by adjusting the settings under "Attributes" to enforce email uniqueness.

This setup should help you merge the accounts under a single user when signing up via different providers. Let me know if you need further assistance!

profile picture
EXPERT
answered 2 months ago
  • Hi Vitor, thank you for your response and idea! I've actually tried a Pre-Signup Trigger, but I've read somewhere that the social provider sign-in doesn't trigger it. It seemed to be wrong... I tried out your solution and there are two issues:

    1. the social provider sign up/ sign-in will be interrupted if there is a match and therefore users need to sign-in with their email and password. However, it would be user-friendly to be able to log-in with the social-provider, if there is a match. Do you know any information that I can look up by my own? I'm new in the AWS environment and it's huge, but i'm eager to learn :)
    2. A Mapping between existing social provider and sign-up via the classic Cognito needs to be done Thanks!

You are not logged in. Log in to post an answer.

A good answer clearly answers the question and provides constructive feedback and encourages professional growth in the question asker.

Guidelines for Answering Questions