amazon-cognitofederated-identity

How to link AWS Cognito native user to the federated user


Currently Cognito allows merging federated users (users logging from external identity providers like Google) to native users (users who signed up via username and password combination).

Is there a way to merge an existing federated user with a new native user?


Solution

  • No. You can only create a link from a user who has never signed in. That is, you can only link the new user at the point they are created (in the pre-auth trigger).

    "This allows you to create a link from the existing user account to an external federated user identity that has not yet been used to sign in".

    Personally I catch this case in the pre-signup trigger, and reject the sign up with a custom message ("An account already exists with this email address, please sign in using Google").

    Here is my pre-signup lambda in case you find it useful

    const AWS = require('aws-sdk');
    const cognito = new AWS.CognitoIdentityServiceProvider();
    
    exports.handler = (event, context, callback) => {
      function checkForExistingUsers(event, linkToExistingUser) {
        console.log('Executing checkForExistingUsers');
    
        var params = {
          UserPoolId: event.userPoolId,
          AttributesToGet: ['sub', 'email'],
          Filter: 'email = "' + event.request.userAttributes.email + '"'
        };
    
        return new Promise((resolve, reject) =>
          cognito.listUsers(params, (err, result) => {
            if (err) {
              reject(err);
              return;
            }
            if (result && result.Users && result.Users[0] && result.Users[0].Username && linkToExistingUser) {
              console.log('Found existing users: ', result.Users);
              if (result.Users.length > 1) {
                result.Users.sort((a, b) => (a.UserCreateDate > b.UserCreateDate ? 1 : -1));
                console.log('Found more than one existing users. Ordered by createdDate: ', result.Users);
              }
              linkUser(result.Users[0].Username, event)
                .then((result) => {
                  resolve(result);
                })
                .catch((error) => {
                  reject(err);
                  return;
                });
            } else {
              resolve(result);
            }
          })
        );
      }
    
      function linkUser(sub, event) {
        console.log('Linking user accounts with target sub: ' + sub + 'and event: ', event);
    
        //By default, assume the existing account is a Cognito username/password
        var destinationProvider = 'Cognito';
        var destinationSub = sub;
        //If the existing user is in fact an external user (Xero etc), override the the provider
        if (sub.includes('_')) {
          destinationProvider = sub.split('_')[0];
          destinationSub = sub.split('_')[1];
        }
        var params = {
          DestinationUser: {
            ProviderAttributeValue: destinationSub,
            ProviderName: destinationProvider
          },
          SourceUser: {
            ProviderAttributeName: 'Cognito_Subject',
            ProviderAttributeValue: event.userName.split('_')[1],
            ProviderName: event.userName.split('_')[0]
          },
          UserPoolId: event.userPoolId
        };
        console.log('Parameters for adminLinkProviderForUser: ', params);
        return new Promise((resolve, reject) =>
          cognito.adminLinkProviderForUser(params, (err, result) => {
            if (err) {
              console.log('Error encountered whilst linking users: ', err);
              reject(err);
              return;
            }
            console.log('Successfully linked users.');
            resolve(result);
          })
        );
      }
    
      console.log(JSON.stringify(event));
    
      if (event.triggerSource == 'PreSignUp_SignUp' || event.triggerSource == 'PreSignUp_AdminCreateUser') {
        checkForExistingUsers(event, false)
          .then((result) => {
            if (result != null && result.Users != null && result.Users[0] != null) {
              console.log('Found at least one existing account with that email address: ', result);
              console.log('Rejecting sign-up');
              //prevent sign-up
              callback('An external provider account alreadys exists for that email address', null);
            } else {
              //proceed with sign-up
              callback(null, event);
            }
          })
          .catch((error) => {
            console.log('Error checking for existing users: ', error);
            //proceed with sign-up
            callback(null, event);
          });
      }
    
      if (event.triggerSource == 'PreSignUp_ExternalProvider') {
        checkForExistingUsers(event, true)
          .then((result) => {
            console.log('Completed looking up users and linking them: ', result);
            callback(null, event);
          })
          .catch((error) => {
            console.log('Error checking for existing users: ', error);
            //proceed with sign-up
            callback(null, event);
          });
      }
    };