Extending Amazon Cognito with Email OTP for 2FA using Amazon SES
This blog post was contributed by Pratik Pednekar@McAfee, Kanishk Mahajan@AWS and Krishnaraj Barvathaya@McAfee
Amazon Cognito is a fully managed AWS service that scales to millions of users and lets you add user sign-up, sign-in, and access control to your web and mobile apps quickly and easily. The two main components of Amazon Cognito are user pools and identity pools. User pools are user directories that provide sign-up and sign-in options for your web and mobile app users. Identity pools provide AWS credentials to grant those users access to other AWS services.
Amazon Simple Email Service (SES) is a fully managed AWS service that lets you send email securely, globally, and at scale. It is a cost-effective, flexible, and scalable email service that enables developers to send mail from within any application.
You can add multi-factor authentication (MFA) to a Cognito user pool to protect the identity of your users. MFA adds a second authentication method that doesn’t rely solely on user name and password. One time passwords (OTPs) are a popular MFA choice for organizations looking to step up their security with two-factor authentication (2FA). OTPs delivered through email and SMS messages are a widely used form of 2FA that many organizations choose for user convenience, ease of administration, and low associated costs.
Cognito user pools currently only support SMS text messages or time-based one-time (TOTP) passwords as second factors in signing in your users and don’t provide out of the box support for the use of email based OTP as an MFA option.
A key feature of Amazon Cognito user pools is the ability to customize authentication flows such as during user sign-up, confirmation, sign-in (authentication) with AWS Lambda functions called Lambda triggers. In this blog post, we demonstrate how to extend Amazon Cognito with email based OTP using SES and AWS Lambda triggers.
In this blog post, you will use the Wild Rydes application from the AWS Identity: Using Cognito for serverless consumer apps workshop as a sample application to demonstrate how to extend Cognito with email based OTP as 2FA.
Note that when using email as a second factor for authentication, we recommend that you use a different channel for account recovery to reduce the risk of account takeover. In order to do that you set the account recovery settings in your user pool to “Phone only” to allow recovery using SMS to a verified phone number or set to “none” which means users will have to contact an administrator to reset their password.
The code snippet below show this property that you will need to add in your amplify-config.js file of the Wild Rydes application.
Define Auth Challenge Lambda Trigger
In this trigger, we layer the email based OTP verification mechanism on top of the Cognito Secure Remote Password (SRP) challenge response protocol that provides username and password authentication. This trigger implements a challenge loop, where the user moves from one challenge to the next. Then the loop repeats (or throws an error response) until all challenges are answered.
Cognito implements the first 2 challenges (SRP_A and PASSWORD_VERIFIER) that complete the username and password authentication flow for the user. We then define the 3rd challenge (CUSTOM_CHALLENGE) for email-based OTP, which triggers the Create Auth Challenge Lambda function that we will describe next. The code below shows the entire Lambda trigger code for the Define Auth Challenge Lambda Trigger in our solution:
Figure 2: Define Auth Challenge Lambda Trigger
Create Auth Challenge Lambda Trigger
This Lambda trigger generates the OTP and then integrates with Amazon SES to send the OTP as an email to the user. It is triggered from the Define Auth Challenge Lambda, described earlier, if the session length is equal to 2 (i.e. just after the PASSWORD_VERIFIER challenge is verified).
Also, in the custom logic, it allows 3 attempts for the user to enter the correct OTP. This is implemented in the code where it checks if the session length is greater than 2; the else clause reads the OTP from the previous session instead of creating and sending a new one. Figure 3 below shows the entire lambda code for the Create Auth Challenge Lambda Trigger in our solution:
Figure 3: Create Auth Challenge Lambda Trigger
Verify Auth Challenge Lambda Trigger
This Lambda trigger validates that the challenge response provided by the client (challengeAnswer parameter) matches the expected response. The expected response from the user is contained in the privateChallengeParameters values that are returned by the Create Auth Challenge Lambda trigger. It then sets the answerCorrect attribute to true if the user successfully completed the challenge, or false otherwise. Figure 4 below shows the entire lambda code for the Verify Auth Challenge Lambda Trigger in our solution:
Figure 4: Verify Auth Challenge Lambda Trigger
In this section, you will go through the steps to deploy, customize, and then test the Wild Rydes application for successfully extending Cognito with email based OTP.
- Step 0: Deploy and run the AWS Identity: Using Cognito for serverless consumer apps workshop. Follow the detailed step by step instructions in the workshop. We recommend that you at least complete and test all steps up to the completion of Module 2 of the workshop. This will provide you with an end to end deployment of using Cognito authentication with API Gateway based authorization for a single page React JS application.
- Step 1: Add a new
authenticationFlowTypeproperty with the value of
CUSTOM_AUTHin the Auth section of the amplify-config.js file. This file can be found in the amazon-cognito-identity-management-workshop/website/src folder of your cloned repository. Here’s how our amplify-config.js file looks like with this parameter.
Figure 5: amplify-config.js file with a new authenticationFlowType parameter
- Step 2: Edit the
onSubmitVerificationmethod of the SignIn.js file found in the amazon-cognito-identity-management-workshop/website/src/auth folder of your cloned repository. Replace
Auth.sendCustomChallengeAnswer. Here’s how our SignIn.js file’s OnSubmitVerification method looks like after this modification.
Figure 6: SignIn.js with the modified onSubmitVerification method
- Step 3: Using SES to send emails is quick and easy. Complete steps 2 and 3 here in this SES Quickstart to verify your email address with Amazon SES and test sending your first email. Before you can send email from your email address through SES, you need to show SES that you own the email address by verifying it.
- Modify the Create Auth Challenge Lambda trigger in Figure 3 with your verified email address from SES. We have it set to a dummy ‘email@example.com’ email address as shown in Figure 7. You need to replace this email address with your verified email address from SES.
Figure 7: Replace the dummy email address in the Create Auth Challenge Lambda trigger with your verified SES email address
- Step 4: Add the three Lambda triggers to the Cognito user pool of the Wild Rydes application. Together, these three triggers orchestrate your customized authentication flow for email based OTP using Amazon SES.
- Navigate to the Amazon Cognito console. Select Manage User Pools and select the WildRydes user pool. Select App clients from the left panel and click on Show Details on the right panel. Under the Auth Flows Configuration section, select the Enable Lambda trigger based custom authentication checkbox as well as the Enable SRP (secure remote password) protocol based authentication (ALLOW_USER_SRP_AUTH) checkbox. Save your changes by clicking on the Save app client changes box at the bottom of the screen.
- Create 3 Node.js Lambda functions from the code that we have supplied you – i.e. create 1 Lambda function each for each of the 3 triggers. If you are new to Lambda, follow the simple instructions here to build Node.js lambda functions from the console . The console creates a Lambda function with a single source file named index.js. Copy/paste the code in Figure 2 to replace the index.js file for your Lambda function that implements Define Auth Challenge Lambda trigger in in the built-in code editor. To save your changes, choose Save and then select Deploy.
- Follow these steps above to create new Lambda functions for the Create Auth Challenge Lambda and Verify Auth Challenge Lambda triggers as well. Copy/paste the code in Figure 3 and Figure 4 respectively to replace the index.js files for the corresponding lambda functions.
- To send SES emails with the lambda function, the attached Lambda execution role must have relevant SES permissions. Attach this permission policy that allows access to SES email sending actions to the Lambda execution role of the Create Auth Challenge Lambda. Follow the steps outlined here to create and modify execution roles.
- Let’s now add each of the 3 Lambda functions as Triggers in the Cognito User Pool. Navigate to the Amazon Cognito console. Select Manage User Pools and select the WildRydes user pool. Next, select Triggers on the left panel of the console for the WildRydes user pool.
- From the right panel, select Define Auth Challenge as a Trigger option and select the Lambda function created in Step 4b that corresponds to it.
- From the right panel, select Create Auth Challenge as a Trigger option and select the Lambda function created in Step 4b that corresponds to it
- From the right panel, select Verify Auth Challenge as a Trigger option and select the Lambda function created in Step 4b that corresponds to it
Test the email-based OTP with a user that has already completed the sign-up process to the Wild Rydes application.
- Go back to the home page of the Wild Rydes application. Click on Giddy up to sign in. Enter the username/password of an existing signed-up user.
Figure 8: Sign in to the Wild Rydes application
Figure 9: Enter username/password of a signed-up user
On successful authentication, you will now be prompted to enter an OTP. Check your SES verified email address inbox and look for an email with a subject of ‘WildRydes-OTP’
Figure 10: Provide email based OTP for MFA (Multi Factor Authentication)
Once signed in, click anywhere on the map of Seattle to indicate a pickup location, then select the Request button to call your ride. You should be informed of your unicorn’s arrival momentarily.
Figure 11: Request your unicorn ride
After you complete the setup and test outlined in this blog post, you can clean up the resources you created to avoid incurring additional charges.
- Follow the detailed steps in Module 4 of the Wild Rydes workshop to clean up all resources.
- If you no longer need to use the SES verified email address then you can delete the email identity in Amazon SES.
Amazon Cognito currently only support SMS text messages or time-based one-time (TOTP) passwords as second factors in signing in your users. However it doesn’t provide out of the box support for the use of email based OTP as an MFA option. A key feature of Cognito user pools is the ability to customize authentication flows such as during user sign-up, confirmation, sign-in (authentication) with Lambda functions called Lambda triggers. In this blog post, we demonstrated how you can extend Amazon Cognito with email based OTP using SES and AWS Lambda triggers.
For more information on extending Cognito using Lambda triggers visit the Cognito documentation to customize user pool workflows with Lambda triggers. Also, as next steps we recommend that you complete the optional extension at the end of Module 1 of the Wild Rydes workshop. These demonstrate how to trigger AWS Lambda functions during user pool operations such as user sign-up, confirmation, and sign-in. You can add authentication challenges, migrate users, and customize verification messages.