1
votes

I am using AWS cognito pool migration using Lambda function with cognito execution role Following is my new pool app client setting

enter image description here

or

enter image description here AWS doc says

User migration authentication flow A user migration Lambda trigger allows easy migration of users from a legacy user management system into your user pool. To avoid making your users reset their passwords during user migration, choose the USER_PASSWORD_AUTH authentication flow. This flow sends your users' passwords to the service over an encrypted SSL connection during authentication.

When you have completed migrating all your users, we recommend switching flows to the more secure SRP flow. The SRP flow does not send any passwords over the network.

I have created lambda function with role "AmazonCognitoPowerUser"

        async function authenticateUser(cognitoISP: CognitoIdentityServiceProvider, username: string, password: string): Promise<User | undefined> {
            console.log(`authenticateUser: user='${username}'`);

            const params: AdminInitiateAuthRequest = {
                AuthFlow: 'ADMIN_USER_PASSWORD_AUTH',
                AuthParameters: {
                    PASSWORD: password,
                    USERNAME: username,
                },
                ClientId: OLD_CLIENT_ID,
                UserPoolId: OLD_USER_POOL_ID,
            };
            const cognitoResponse = await cognitoISP.adminInitiateAuth(params).promise();
            const awsError: AWSError = cognitoResponse as any as AWSError;
            if (awsError.code && awsError.message) {
                console.log(`authenticateUser: error ${JSON.stringify(awsError)}`);
                return undefined;
            }
            console.log(`authenticateUser: found ${JSON.stringify(cognitoResponse)}`);

            return lookupUser(cognitoISP, username);
        }

        async function lookupUser(cognitoISP: CognitoIdentityServiceProvider, username: string): Promise<User | undefined> {
            console.log(`lookupUser: user='${username}'`);
            const params = {
                UserPoolId: OLD_USER_POOL_ID,
                Username: username,
            };
            const cognitoResponse = await cognitoISP.adminGetUser(params).promise();
            const awsError: AWSError = cognitoResponse as any as AWSError;
            if (awsError.code && awsError.message) {
                console.log(`lookupUser: error ${JSON.stringify(awsError)}`);
                return undefined;
            }
            console.log(`lookupUser: found ${JSON.stringify(cognitoResponse)}`);

            const userAttributes = cognitoResponse.UserAttributes ? cognitoResponse.UserAttributes.reduce((acc, entry) => ({
                ...acc,
                [entry.Name]: entry.Value,
            }), {} as {[key: string]: string | undefined}) : {};
            const user: User = {
                userAttributes,
                userName: cognitoResponse.Username,
            };
            console.log(`lookupUser: response ${JSON.stringify(user)}`);
            return user;
        }

        async function onUserMigrationAuthentication(cognitoISP: CognitoIdentityServiceProvider, event: CognitoUserPoolTriggerEvent) {
            // authenticate the user with your existing user directory service
            const user = await authenticateUser(cognitoISP, event.userName!, event.request.password!);
            if (!user) {
                throw new Error('Bad credentials');
            }

            event.response.userAttributes = {
                // old_username: user.userName,
                // 'custom:tenant': user.userAttributes['custom:tenant'],
                email: user.userAttributes.email!,
                email_verified: 'true',
                preferred_username: user.userAttributes.preferred_username!,
            };
            event.response.finalUserStatus = 'CONFIRMED';
            event.response.messageAction = 'SUPPRESS';

            console.log(`Authentication - response: ${JSON.stringify(event.response)}`);
            return event;
        }

        async function onUserMigrationForgotPassword(cognitoISP: CognitoIdentityServiceProvider, event: CognitoUserPoolTriggerEvent) {
            // Lookup the user in your existing user directory service
            const user = await lookupUser(cognitoISP, event.userName!);
            if (!user) {
                throw new Error('Bad credentials');
            }

            event.response.userAttributes = {
                // old_username: user.userName,
                // 'custom:tenant': user.userAttributes['custom:tenant'],
                email: user.userAttributes.email!,
                email_verified: 'true',
                preferred_username: user.userAttributes.preferred_username!,
            };
            event.response.messageAction = 'SUPPRESS';

            console.log(`Forgot password - response: ${JSON.stringify(event.response)}`);

            return event;
        }

        export const handler = async (event: CognitoUserPoolTriggerEvent, context: Context): Promise<CognitoUserPoolTriggerEvent> => {
            const options: CognitoIdentityServiceProvider.Types.ClientConfiguration = {
                region: OLD_USER_POOL_REGION,
            };
            if (OLD_ROLE_ARN) {
                options.credentials = new ChainableTemporaryCredentials({
                    params: {
                        ExternalId: OLD_EXTERNAL_ID,
                        RoleArn: OLD_ROLE_ARN,
                        RoleSessionName: context.awsRequestId,
                    },
                });
            }
            const cognitoIdentityServiceProvider = new CognitoIdentityServiceProvider(options);

            switch (event.triggerSource) {
                case 'UserMigration_Authentication':
                    return onUserMigrationAuthentication(cognitoIdentityServiceProvider, event);
                case 'UserMigration_ForgotPassword':
                    return onUserMigrationForgotPassword(cognitoIdentityServiceProvider, event);
                default:
                    throw new Error(`Bad triggerSource ${event.triggerSource}`);
            }
        }

and added trigger into new pool,

enter image description here

After many attempt Lambda trigger is not working on login always getting error .

  {__type: "NotAuthorizedException", message: "Incorrect username or password."}
       message: "Incorrect username or password."
     __type: "NotAuthorizedException"

Though its working fine if we use forget passwords flow after reset password user migrated to new pool

UPDATED :

When directly run test on lambda using following json

  {
   "version": "1",
   "triggerSource": "UserMigration_Authentication",
   "region": "ap-south-1",
   "userPoolId": "ap-XXXXXXXXX2",
   "userName": "[email protected]",
   "callerContext": {
   "awsSdkVersion": "aws-sdk-unknown-unknown",
   "clientId": "1XXXXXXXXXXXXXXXXXXfgk"
   },
   "request": {
   "password": "vkhan",
   "validationData": null,
   "userAttributes": null
    },
   "response": {
    "userAttributes": null,
    "forceAliasCreation": null,
    "finalUserStatus": null,
    "messageAction": null,
    "desiredDeliveryMediums": null
    }
  }

Then getting following response and user migrated into new pool means we have issue in trigger during login.

 INFO   Authentication - response: {"userAttributes":{"email":"[email protected]","email_verified":"true"},"forceAliasCreation":null,"finalUserStatus":"CONFIRMED","messageAction":"SUPPRESS","desiredDeliveryMediums":null}
2

2 Answers

0
votes

With Lambda trigger, Cognito service invokes Lambda function. So Cognito will require permission to invoke Lambda function. How are you configuring the Lambda trigger on your userpool? If you are using AWS Cognito console, the permission should be set automatically.

You may also verify if the user already exists in the new userpool that could cause such behavior.

0
votes

Please verify that your application is using OAUTH flow USER_PASSWORD instead of the default USER_SRP_AUTH.

For reference: link