今天的主角是 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 會在以下幾種情境被觸發:
- 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.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 執行
如果沒執行成功可以檢測以下幾點
- AWS Cognito 是否有 Invoke Lambda 權限
- Lambda 是否有 KMS decrypt 權限
- AWS Cognito MFA 不能是
Off
,必須是Optional
或Required
- AWS Cognito verification 不能是
No verification
另外,這整個設定過程都是透過 aws-cli 直接對 Cognito API 修改完成,如果在 AWS Cognito Console 修改「MFA and verifications」內的值則會覆寫 --lambda-config
內的值,可以再重複用 cognito-idp describe-user-pool
重新檢查是否參數正確。