How do I use the custom email sender Lambda trigger in Amazon Cognito user pools?

7 minute read
0

I want to invoke the Amazon Cognito custom email sender AWS Lambda trigger to process and deliver email messages.

Resolution

Use an Amazon Cognito custom email sender trigger to allow third-party providers to send email notifications to your users from your Lambda function code. Amazon Cognito sends email message requests to a Lambda function. Then, the Lambda function processes and delivers the email messages.

Create a Lambda function to be your custom email sender trigger

  1. Create a file with the name index.js, add the following code to the file, and then save your changes.

    const AWS = require('aws-sdk');const b64 = require('base64-js');
    const encryptionSdk = require('@aws-crypto/client-node');
    
    // Configure the encryption SDK client with the KMS key from the environment variables.
    
    const { encrypt, decrypt } = encryptionSdk.buildClient(encryptionSdk.CommitmentPolicy.REQUIRE_ENCRYPT_ALLOW_DECRYPT);
    const generatorKeyId = process.env.KEY_ALIAS;
    const keyIds = [ process.env.KEY_ARN ];
    const keyring = new encryptionSdk.KmsKeyringNode({ generatorKeyId, keyIds })
    exports.handler = async (event) => {
    
    // Decrypt the secret code using encryption SDK.
    let plainTextCode;
    if(event.request.code){
    const { plaintext, messageHeader } = await decrypt(keyring, b64.toByteArray(event.request.code));
    plainTextCode = plaintext
    }
    
    // PlainTextCode now has the decrypted secret.
    
    if(event.triggerSource == 'CustomEmailSender_SignUp'){
    
    // Send email to end-user using custom or 3rd party provider.
    // Include temporary password in the email.
    
    console.log("CustomEmailSender_SignUp: " + plainTextCode);
    
    }else if(event.triggerSource == 'CustomEmailSender_ResendCode'){
    console.log("CustomEmailSender_ResendCode: " + plainTextCode);
    
    }else if(event.triggerSource == 'CustomEmailSender_ForgotPassword'){
    console.log("CustomEmailSender_ForgotPassword: " + plainTextCode);
    
    }else if(event.triggerSource == 'CustomEmailSender_UpdateUserAttribute'){
    console.log("CustomEmailSender_UpdateUserAttribute: " + plainTextCode);
    
    }else if(event.triggerSource == 'CustomEmailSender_VerifyUserAttribute'){
    console.log("CustomEmailSender_VerifyUserAttribute: " + plainTextCode);
    
    }else if(event.triggerSource == 'CustomEmailSender_AdminCreateUser'){
    console.log("CustomEmailSender_AdminCreateUser: " + plainTextCode);
    
    }else if(event.triggerSource == 'CustomEmailSender_AccountTakeOverNotification'){
    console.log("CustomEmailSender_AccountTakeOverNotification: " + plainTextCode);
    }
    
    return;
    };

    Warning: This code is scripted to decrypt encrypted secrets. The secrets are temporary passwords and authorization codes sent by Amazon Cognito. The script also prints the plaintext code in logs for demonstration purposes. However, logging secrets in plaintext is a security issue. When your application is in production, NEVER print decrypted secrets in logs.
    Add the relevant code in the index.js file for sending email messages to users from your custom email provider.

  2. Create a .zip file archive, also known as a compressed index.js file:

    $ zip function.zip index.js
  3. Open the Lambda console.

  4. Create a Lambda function with a Node.js runtime.
    The following is an example AWS Command Line Interface (AWS CLI) command to create a Lambda function with Node.js 14.x runtime.
    Note: If you receive errors when running AWS CLI commands, make sure that you're using the most recent version of the AWS CLI.

    $ aws lambda create-function --function-name CustomEmailSender --runtime nodejs14.x --handler index.handler --zip-file fileb://function.zip --role ExecutionRoleARN --region REGION

    Note: Replace ExecutionRoleARN with the AWS Identity and Access Management (IAM) role Amazon Resource Name (ARN) that you want to associate with the Lambda function. For more details, see Lambda execution role. You can replace CustomEmailSender with the function name of your choice. Replace REGION with your AWS Region code.

  5. Install the @aws-crypto/client-node and base64-js module to use the AWS Encryption SDK.
    If you don't have the @aws-crypto/client-node module installed, then do the following. Be sure to run the installation commands in a local development environment that's compatible with Lambda:
    Create a working directory:

    $ mkdir -p aws-crypto-layer/nodejs

    Change the working directory:

    $ cd aws-crypto-layer/nodejs

    Install the latest version of the aws-crypto module with the npm package manager:

    $ npm install @aws-crypto/client-node
    
    $ npm install base64-js

    Note: It's a best practice to use an Amazon Linux 2 environment when you develop Lambda resources. Use Docker to install the aws-crypto module when you develop on a Windows or macOS operating system:

    $ docker run --entrypoint "" -v "$PWD":/var/task "public.ecr.aws/lambda/nodejs:14" /bin/sh -c "npm install @aws-crypto/client-node; exit"

    Create a .zip file to upload to your Lambda layer:

    $ zip -r ../package.zip ../

    Create a Lambda layer that includes the latest version of the @aws-crypto/client-node module:

    $ aws lambda publish-layer-version --layer-name node_crypto --description "My layer" --license-info "MIT" --compatible-runtimes nodejs14.x --zip-file fileb://../package.zip --region REGION

    Add the Lambda layer to your function:

    $ aws lambda update-function-configuration --function-name CustomEmailSender --layers arn:aws:lambda:us-east-2:123456789012:layer:node_crypto:1 --region REGION

    Note: Replace CustomEmailSender with the name of your function. Replace arn:aws:lambda:us-east-2:123456789012:layer:node_crypto:1 with the LayerVersionArn value from the publish-layer-version command output. Replace REGION with your AWS Region code.

Create an encryption key in AWS KMS

Amazon Cognito uses an AWS Key Management Service (AWS KMS) symmetric encryption key to encrypt temporary passwords and authorization codes generated by Amazon Cognito.

  1. Use the following command to create an AWS KMS key:

    $ aws kms create-key --description "KMS Key for CustomEmailSender" --region REGION

    Note: Replace REGION with your Region code.
    Because this command doesn't specify a key policy, the default key policy is assigned. To apply a customized key policy to your AWS KMS key, add the --policy parameter to the command with your JSON policy. Make sure that you grant kms:CreateGrant permission to the IAM entity that performs the UpdateUserPool operation to add a custom email trigger.
    You can't specify an alias when you run the create-key command. To create an alias for the new KMS key, use the create-alias command:

    $ aws kms create-alias --alias-name alias/custom-email-key --target-key-id KeyId --region REGION

    Note: Replace Keyid with the key ID value from the create-key command output. Replace REGION with your Region code.
    The code example relies on the environment variables KEY_ALIAS and KEY_ARN for encryption key information. You must configure these environment variables in the CustomEmailSender Lambda trigger.

    $ aws lambda update-function-configuration --function-name CustomEmailSender --environment 'Variables={KEY_ALIAS=alias/custom-email-key,KEY_ARN=key_ARN}' --region REGION

    Note: Replace CustomEmailSender with your Lambda function name. Replace alias/custom-email-key with the key alias for your AWS KMS key. Replace key_ARN with the ARN value from the create-key command output. Replace REGION with your Region code.

  2. Grant the Amazon Cognito service principal the cognito-idp.amazonaws.com permission to invoke the Lambda function Use the following command to grant permission to the Amazon Cognito service principal that invokes the Lambda function.

    $ aws lambda add-permission --function-name CustomEmailSender --statement-id "CognitoLambdaInvokeAccess" --action lambda:InvokeFunction --principal cognito-idp.amazonaws.com --source-arn UserPoolArn --region REGION

    Note: Replace CustomEmailSender with your Lambda function name. Replace UserPoolArn with your user pool ARN. To find the user pool ARN, open the Amazon Cognito console User pools page. Then, select the user pool from the list. Or, run the describe-user-pool command. Replace REGION with your Region code.

  3. Update the Amazon Cognito user pool so that it uses a custom email sender Lambda trigger Set the CustomEmailSender parameter in the UpdateUserPool API. UpdateUserPool requires all the existing parameters of your user pool and the parameters you want to change. Amazon Cognito sets the values of any missing parameters to their defaults. For more information, see Updating user pool configuration.
    In the following example, only the --lambda-config parameter is used with CustomEmailSender Lambda function details. So, the command adds the custom email sender trigger to your user pool and sets the remaining user pool parameters to default. If you configure your user pool with non-default values, then pass the values in the update-user-pool command to avoid setting them to default.

    $ aws cognito-idp update-user-pool --lambda-config "CustomEmailSender={LambdaVersion=V1_0,LambdaArn= LambdaARN},KMSKeyID=KMSKeyARN" --user-pool-id UserPoolId --region REGION

    Note: Replace LambdaARN with the function's ARN value from the create-function command output. Replace KMSKeyARN with the ARN value provided in the create-key command output. Replace UserPoolId with your Amazon Cognito user pool ID. Replace REGION with your Region code.

Test the functionality

To test the Lambda function's integration, simulate an email sender operation for a user from your user pool, such as email verification or password recovery. The following example shows a ForgotPassword event sent to the Lambda function by Amazon Cognito.

{  version: '1',
  triggerSource: 'CustomEmailSender_ForgotPassword',
  region: 'us-east-1',
  userPoolId: 'us-east-1_xxxxxxxx',
  userName: 'example_user',
  callerContext: {
    awsSdkVersion: 'aws-sdk-unknown-unknown',
    clientId: '12a3b4example-clientid'
  },
  request: {
    type: 'customEmailSenderRequestV1',
    code: 'XXXXeBlI7XP3RQmipedVF+7OGa4AgQACABVhdXXXXXXXvLXB1YmxpYy1rZXkAREF6Zk9NR2lBR0FUeDRITStmRHl4RDJyNlpqa2wvWktBbG45ckRmTEpMZ1A3THp4ME9RaVVjVHl3MVFOSEZjS3piZz09AAt1c2VycG9vbC1pZAATdXMtZWFzdC0xX29DOUhnUHVKWgABAAdhd3Mta21zAEthcm46XXdzOmttczp1cy1lYXN0LTE6XXX3XXc0NDA5OXXzOmtleS8yNmQ0ZjVmMy00YmZhLTQ0OXXtODUxZS01ZTM2ZWIwYjhmYjMAuAECAQB42Am0o+Rx0MgG+wLLyKtm1/vTm03JK3jQBZxqABAkreYBMOoAUtm3mLS7+kb2VL0SHgAAAH4wfAYJKoZIhvcNAQcGoG8wbQIBADBoBgkqhkiG9w0BBwEwHgYJYIZIAWUDBAEuMBEEDHjPPHlSWPt+UKrQOQIBEIA7oFUeGe2NPX2cuEVi+Qxwp8/IH1bgyYQut+QbbkElc1rXXXXXbeEVMjMkFQmQufmJyag9v1f+PUalvXwXAAAAAAwAABAAAAAAAAAAAAAAAAAAscZHg8dY/cKTskGQc065mv////8AAAABAAAAAAAAAAAAAAABAAAABpofXaVnP4pmf+yMoCElrOGy7Gn8pIUAZzBlAjEAn/7tuTNko8/HCwXXXXlCOJDeU0SSyB7o9y0TXHM7GptdvmB1JL9OzLxmUg6zChIhAjAFFbH4NrSblvwh/m0inDc11BpeOSKghtg8Pg5Nkf8eY6vmXX6GxjaCuyhBSO7IDcM=',
    clientMetadata: null,
    userAttributes: {
      sub: '1a2b3cde-33cd-402f-5g67-examplesub',
      'cognito:user_status': 'CONFIRMED',
      email_verified: 'true',
      email: 'user@example.com'
    }
  }
}

The following example is a response in plaintext code:

CustomEmailSender_ForgotPassword: 12345

You can see the complete logs in the Amazon CloudWatch log group created for your Lambda function. For more details on how to see these logs, see Using Amazon CloudWatch logs with AWS Lambda.
Install the @aws-crypto/client-node and base64-js module to use the AWS Encryption SDK.

AWS OFFICIAL
AWS OFFICIALUpdated 3 months ago
2 Comments

Important note

While running the update-user-pool command, please be very mindful. Please refer this document for more information: https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pool-updating.html#cognito-user-pool-updating-api-cli

It states:

If you don't provide values for existing parameters, Amazon Cognito sets them to default values. For example, when you have existing LambdaConfig and you submit an UpdateUserPool with an empty LambdaConfig, you delete the assignment of all Lambda functions to user pool triggers. Plan accordingly when you want to automate changes to your user pool configuration.

AWS
SUPPORT ENGINEER
Tarit_G
replied 9 months ago