Site icon Mr. 沙先生

How to custom sender Email/SMS with 3rd party in AWS Cognito

今天的主角是 AWS Cognito 這個服務,有一個客戶提到 AWS Cognito 提供兩種方法寄送 Email 給使用者:(1) AWS Cognito 內建的發信機制 (2) AWS SES。在多數情況下 AWS SES 提供了很不錯的寄信需求,但是仍有少數情況必須客製化,例如:希望沿用原有的 3rd-party 發信機制、使用 AWS SDK 送 AWS SES 信件等等,這篇花了一些時間 demo 如何透過 AWS Cognito 的 Custom Email Lambda Trigger 來達到 3rd-party 的做法以及中間過程中踩過的坑。

AWS Cognito 的驗證與註冊流程都會需要 users 提供驗證訊息,在原有的 workflow 內提供兩個 Lambda trigger 可以插入客製化需求 Custom Email Lambda Trigger、Custom SMS Lambda Trigger 而這隻 Lambda 會在以下幾種情境被觸發:

這兩隻 Custom sender Lambda 會攔截上述幾種需要發通知給 users 的動作,與使用者互動完成後才會繼續下一步動作。

How to custom sender with AWS Lambda

第一步先看看 Lambda trigger 時收到的 events 範例:

{
    "version": "1",
    "triggerSource": "CustomEmailSender_SignUp",
    "region": "us-east-1",
    "userPoolId": "us-east-1_BKjaDEF6p",
    "userName": "54cf7eb7-0b96-4304",
    "callerContext": {
      "awsSdkVersion": "aws-sdk-nodejs-2.856.0",
      "clientId": "6u7c9vr3pkstoog..."
    },
    "request": {
      "type": "customEmailSenderRequestV1",
      "code": "AYADeILxywKhhaq8Ys4mh0aHutYAgQAC...",
      "clientMetadata": null,
      "userAttributes": {
        "sub": "54cf7eb7-0b96-4304-8d6b",
        "email_verified": "true",
        "cognito:user_status": "CONFIRMED",
        "cognito:email_alias": "user@mail.com",
        "phone_number_verified": "false",
        "phone_number": "...",
        "given_name": "Scott",
        "family_name": "Ivanov",
        "email": "user@mail.com"
      }
    }
}

以 CustomEmailSender_SignUp 這個 triggerSource 來說需要使用者拿到 6 位數的驗證碼,然後再輸入在瀏覽器的驗證框上,所以在這裡會用到 request.userAttributes.email 和 request.code 這兩個欄位。

Create Lambda function integrate AWS Cognito

作者以 aws-sdk 發 AWS SES 作為 3rd-party 的範例,詳細範例可以參考 shazi7804/aws-cognito-custom-sender-lambda:

const aws = require('aws-sdk')
const b64 = require('base64-js')
const encryptionSdk = require('@aws-crypto/client-node')
const nodemailer = require("nodemailer")

const { decrypt } = encryptionSdk.buildClient(encryptionSdk.CommitmentPolicy.REQUIRE_ENCRYPT_ALLOW_DECRYPT);
const generatorKeyId = process.env.KEY_ALIAS;
const keyIds = [ process.env.KEY_ID ];
const keyring = new encryptionSdk.KmsKeyringNode({ generatorKeyId, keyIds })
const sender = process.env.SENDER

var ses = new aws.SES({ region: process.env.AWS_REGION });

exports.handler = async(event) => {
    let plainTextCode
    if (event.request.code) {
        const { plaintext, messageHeader } = await decrypt(keyring, b64.toByteArray(event.request.code))
        plainTextCode = plaintext
    }

    // Custom email provider when triggerSource is 'CustomEmailSender_SignUp'
    if (event.triggerSource == 'CustomEmailSender_SignUp') {
        var msg = {
            Destination: {
                ToAddresses: [event.request.userAttributes.email],
            },
            Message: {
                Body: {
                    Text: { Data: `Your code is : ${plainTextCode.toString()}` },
                },
                Subject: { Data: "Mail from Cognito @shazi7804" },
            },
            Source: sender,
        };    
    }
     
    return ses.sendEmail(msg).promise()
    
}

Lambda function 從 event.request.code 拿到的 6 位數代碼必須用 AWS Encryption SDK 跟 AWS KMS decrypt 才能拿到明碼資訊,判斷來自 CustomEmailSender_SignUp 這個 triggerSource 時再調整特定的 email format。

而 AWS Cognito 要能 Invoke Lambda 必須添加以下權限

$ aws lambda add-permission --function-name ${LAMBDA_ARN} \
                            --statement-id "CognitoLambdaInvokeAccess" \
                            --action lambda:InvokeFunction \
                            --principal cognito-idp.amazonaws.com

設定 AWS Cognito 的 Custom Email Lambda Trigger,這步驟目前不支援 Console 設定 … 只能用 aws-cli 操作

$ aws cognito-idp update-user-pool --user-pool-id us-east-1_BKjaDEF6p \
                                   --lambda-config "CustomEmailSender={LambdaVersion=V1_0,LambdaArn=arn:aws:lambda...},KMSKeyID=arn:aws:kms...:key/...-6479ceda1f4f"

Example

aws cognito-idp update-user-pool –user-pool-id us-east-1_BKjaDEF6p –lambda-config “CustomEmailSender={LambdaVersion=V1_0,LambdaArn=arn:aws:lambda:us-east-1:0123456789:function:CognitoCustomSender},KMSKeyID=arn:aws:kms:us-east-1:0123456789:key/e3c84dc4-9f89-4092-8b5c-6479ceda1f4f”

特別提一下 LambdaVersion 固定是 V1_0 不需更改,更新成功後建議用 cognito-idp describe-user-pool 檢查是否更新成功

$ aws cognito-idp describe-user-pool --user-pool-id us-east-1_BKjaDEF6p --query UserPool

...
    "LambdaConfig": {
        "CustomEmailSender": {
            "LambdaVersion": "V1_0",
            "LambdaArn": "arn:aws:lambda:...:function:CognitoCustomSender"
        },
        "KMSKeyID": "arn:aws:kms:...:key/...-6479ceda1f4f"
    },
...

設定完成後可以測試一下註冊新使用者時 6 位數代碼是否會觸發 Lambda 執行

如果沒執行成功可以檢測以下幾點

  1. AWS Cognito 是否有 Invoke Lambda 權限
  2. Lambda 是否有 KMS decrypt 權限
  3. AWS Cognito MFA 不能是 Off,必須是 Optional 或 Required
  4. AWS Cognito verification 不能是 No verification

另外,這整個設定過程都是透過 aws-cli 直接對 Cognito API 修改完成,如果在 AWS Cognito Console 修改「MFA and verifications」內的值則會覆寫 --lambda-config 內的值,可以再重複用 cognito-idp describe-user-pool 重新檢查是否參數正確。

References

Exit mobile version