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

2021-11-16 AWS

今天的主角是 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 TriggerCustom SMS Lambda Trigger 而這隻 Lambda 會在以下幾種情境被觸發:

  • SignUp
  • ResendCode
  • ForgotPassword
  • UpdateUserAttribute
  • VerifyUserAttribute
  • AdminCreateUser
  • AccountTakeOverNotification

這兩隻 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.emailrequest.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,必須是 OptionalRequired
  4. AWS Cognito verification 不能是 No verification

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

References

給 Mr. 沙先生一點建議

彙整

分類

展開全部 | 收合全部

License

訂閱 Mr. 沙先生 的文章

輸入你的 email 用於訂閱