Streamlining AWS Cognito Verification: Custom SMS and Email Solutions Using Lambda
AWS Cognito is a widely-used service that simplifies adding user sign-up, sign-in, and access control to web and mobile applications. It handles the complexities of user authentication and authorization, allowing developers to focus on their application’s functionality. With AWS Cognito, integrating user management and authentication is quick and ensures secure and scalable user verification. It also offers features like multi-factor authentication and social identity provider integration, making it a robust solution for managing user identities.
At our software company, we’ve been developing innovative solutions for our clients, with a particular focus on serverless applications over the past two years. Our experience shows that leveraging serverless technology like AWS Cognito offers several key benefits, including significantly faster software delivery.
In AWS Cognito, the basic authentication flow involves a user signing up, followed by the verification of their email, phone number, or both. This information is then securely stored in Cognito. While this process is straightforward, it offers many customization opportunities.
One area you can customize is the content of the messages sent to users for verification. For email verification, AWS Cognito can be integrated with Amazon SES (Simple Email Service), allowing you to tailor the sender information and configuration sets to match your branding and communication needs. This ensures that the emails sent to users are professional and aligned with your brand’s identity.
For SMS verification, AWS Cognito uses Amazon SNS (Simple Notification Service). However, customization options for SMS are more limited compared to email. Specifically, you cannot change the default Sender ID for SMS messages. If no specific Sender ID is provided, Cognito will send SMS messages from “Notice” by default.
Recently, we encountered a requirement that prompted us to explore the capabilities of AWS Cognito’s custom lambda triggers. We needed to modify the sender ID for SMS verification messages, and we wanted to determine if this could be achieved using custom lambda triggers.
AWS Cognito’s custom lambda trigger functionality proved to be a game-changer for us. While the Cognito console displays several default lambda triggers like sign up, messaging , authentication, and custom authentication, it doesn’t showcase all available triggers.
If you want to learn more about lambda triggers, there is a great article you can read at this link: Amazon Cognito Triggers: All You Need to Know About Them (Part 1).
Initially, we thought we could utilize the Messaging trigger in AWS Cognito and use SNS to send the verification messages. However, things didn’t go as planned. It turns out that the Messaging trigger functions differently than we expected. The Messaging trigger can only be used to add customization and localization to verification, recovery, and multi-factor authentication (MFA) messages. Essentially, while you can customize the message content, the actual sending of SMS or email is handled by Cognito itself.
When customizing these messages, you have to include a placeholder {####}, where Cognito will automatically replace it with the verification code. This limitation means that while you can tailor the message’s content and language to better fit your brand or user base, you cannot change the underlying sender ID or the mechanism by which the message is sent.
To modify the sender ID for SMS messages in AWS Cognito, we need to use Custom Sender Lambda triggers. These triggers give us the flexibility to control how verification messages are sent, allowing us to specify custom sender IDs and manage the delivery process.
For SMS messages, the Custom SMS Sender Lambda trigger lets us set a custom sender ID and handle the sending process via Amazon SNS with our settings. For email messages, the Custom Email Sender Lambda trigger enables integration with Amazon SES to customize the sender address.
For implementation details, refer to the AWS documentation:
- [Custom Sender Lambda Triggers](https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-lambda-custom-sender-triggers.html)
- [Custom SMS Sender Lambda](https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-lambda-custom-sms-sender.html)
To modify the sender ID for SMS messages in AWS Cognito, we need to use Custom Sender Lambda triggers and a symmetric KMS key. Here’s how we can achieve this:
1. Create a Symmetric KMS Key: First, create a symmetric KMS key that will be used to encrypt and decrypt the verification codes.
2. Set Up the Lambda Trigger in Cognito: Configure AWS Cognito to use a custom Lambda trigger for sending verification messages.
3. Lambda Role and KMS Key Permissions: Ensure the Lambda function’s execution role is added to the KMS key user group. This allows the Lambda function to use the KMS key for decryption. Also ensure cognito is able to invoke the lambda function and lambda is able to use KMS for decryption.
4. Encryption and Decryption Process:
— AWS Cognito generates a random verification code.
— The code is then encrypted using the KMS key.
— In the Lambda function, decrypt the encrypted code using the same KMS key and AWS Encryption SDK to get the verification code in plain text.
— Use the Lambda function to send the decrypted verification code to the user via Amazon SNS, specifying the desired sender ID.
KMS CMK creation using tf:
resource "aws_kms_key" "cognito_verification_key" {
description = "KMS key for encrypting verification codes for AWS Cognito"
is_enabled = true
}
resource "aws_kms_alias" "cognito_verification_alias" {
name = "alias/cognitoVerificationKey"
target_key_id = aws_kms_key.cognito_verification_key.id
}
The lambda role permission to execute from cognito:
resource "aws_lambda_permission" "cognito" {
statement_id = "AllowExecutionFromCognito"
action = "lambda:InvokeFunction"
function_name = module.lambda_function.lambda_function_name
principal = "cognito-idp.amazonaws.com"
source_arn = "arn:aws:cognito-idp:${var.region}:${var.account}:userpool/${var.cognito_user_pool}"
}
Cognito configuration for custom sms sender trigger:
lambda_config = {
custom_sms_sender = {
lambda_arn = "arn:aws:lambda:${var.region}:${var.account}:function:${var.environment}_custom_sms_sender_trigger"
lambda_input = {
"smsType" = "Transactional"
}
lambda_version = "V1_0"
}
kms_key_id = "arn:aws:kms:${var.region}:${var.account}:key/${var.kms_cmk_key_id}"
}
Lambda policy to decrypt and sns publish:
resource "aws_iam_policy" "lambda_policy" {
name = "lambda_policy"
description = "Policy for Lambda to access KMS and SNS"
policy = jsonencode({
Version = "2012-10-17",
Statement = [
{
Action = [
"kms:Decrypt",
"kms:Encrypt"
],
Effect = "Allow",
Resource = aws_kms_key.cognito_verification_key.arn
},
{
Action = [
"sns:Publish"
],
Effect = "Allow",
Resource = "*"
}
]
})
}
The lambda role must be in Key users group, or else the lambda wont be able to use the CMK key to decrypt the code.
The lambda code should be something like this:
import aws_encryption_sdk
from aws_encryption_sdk import CommitmentPolicy
import base64
client = aws_encryption_sdk.EncryptionSDKClient(
commitment_policy=CommitmentPolicy.REQUIRE_ENCRYPT_ALLOW_DECRYPT
)
kms_key_provider = aws_encryption_sdk.StrictAwsKmsMasterKeyProvider(key_ids=[
'arn:aws:kms:ap-northeast-1:xxx:key/xxx'
])
def lambda_handler(event, context):
print("event: ", event)
code = event["request"]["code"]
ciphertext = base64.b64decode(code)
plaintext, header = client.decrypt(
source=ciphertext,
key_provider=kms_key_provider
)
print(plaintext)
To use the aws_encryption_sdk
in your Lambda function, you can indeed create a Lambda layer by downloading the SDK locally and then uploading it to AWS Lambda. This way, you ensure that all dependencies are packaged and available for your Lambda function.
For more details: https://dev.classmethod.jp/articles/decrypting-secrets-using-the-aws-encryption-sdk-for-python-in-cognito-custom-email-sender-lambda-trigger/
Now you should be able to get the whole event print in cloudwatch log. and you should be able to see code key and the encrypted value. The lambda use aws_encryption_sdk and kms to decrypt the code and print the plaintext value. we can use the plaintext value to send a sms to the users.