amazon-web-servicesaws-lambdaamazon-cognitogoogle-signinamazon-cognito-facebook

AWS Cognito: Best practice to handle same user (with same email address) signing in from different identity providers (Google, Facebook)


When signing in a user with the same email address through the Google and Facebook identity providers, AWS Cognito creates multiple entries in the user pool, one entry per identity provider used:

Screenshot of AWS Cognito user pool

I have used the example code provided in this tutorial to set up AWS Cognito: The Complete Guide to User Authentication with the Amplify Framework


Solution

  • Yes. You can do it by using AdminLinkProviderForUser https://docs.aws.amazon.com/cognito-user-identity-pools/latest/APIReference/API_AdminLinkProviderForUser.html

    The idea is:

    1. In PreSignUp lambda hook, we Link Provider to User if User already signed up. E.g:
    import CognitoIdentityServiceProvider from 'aws-sdk/clients/cognitoidentityserviceprovider'
    
    const cognitoIdp = new CognitoIdentityServiceProvider()
    const getUserByEmail = async (userPoolId, email) => {
     const params = {
       UserPoolId: userPoolId,
       Filter: `email = "${email}"`
     }
     return cognitoIdp.listUsers(params).promise()
    }
    
    const linkProviderToUser = async (username, userPoolId, providerName, providerUserId) => {
     const params = {
       DestinationUser: {
         ProviderAttributeValue: username,
         ProviderName: 'Cognito'
       },
       SourceUser: {
         ProviderAttributeName: 'Cognito_Subject',
         ProviderAttributeValue: providerUserId,
         ProviderName: providerName
       },
       UserPoolId: userPoolId
     }
    
     const result = await (new Promise((resolve, reject) => {
       cognitoIdp.adminLinkProviderForUser(params, (err, data) => {
         if (err) {
           reject(err)
           return
         }
         resolve(data)
       })
     }))
    
     return result
    }
    
    exports.handler = async (event, context, callback) => {
     if (event.triggerSource === 'PreSignUp_ExternalProvider') {
       const userRs = await getUserByEmail(event.userPoolId, event.request.userAttributes.email)
       if (userRs && userRs.Users.length > 0) {
         const [ providerName, providerUserId ] = event.userName.split('_') // event userName example: "Facebook_12324325436"
         await linkProviderToUser(userRs.Users[0].Username, event.userPoolId, providerName, providerUserId)
       } else {
         console.log('user not found, skip.')
       }
    
     }
     return callback(null, event)
    }
    
    1. Then when user use OAuth with Facebook/Google with User Pool, the Pool will return this User linked.

    Note: You may see 2 records in User Pool UI, but when access User record detail, They already merged.