In this walkthrough, you'll learn how create a PreSignUp Lambda for Cognito in AWS Amplify. When a user signs up with email as an optional attribute, we'll fire up our PreSignUp trigger to search Cognito for users who already signed up with that email attribute.
What you need to get started
That's it, Lezzzgo!
$ npx create-react-app pre-signup
$ cd pre-signup
$ yarn add aws-amplify
$ yarn add aws-amplify-react
Next we'll intialize amplify
$ amplify init
? Enter a name for the project: presignup
? Enter a name for the environment: dev
? Choose your default editor: Visual Studio Code
? Choose the type of app that you're building javascript
Please tell us about your project
? What javascript framework are you using react
? Source Directory Path: src
? Distribution Directory Path: build
? Build Command: npm run-script build
? Start Command: npm run-script start
Using default provider awscloudformation
? Do you want to use an AWS profile? Yes
? Please choose the profile you want to use: default
After initializing our project, we'll add authentication. When we run amplify add auth
we'll do a manual configuration so that we can add our pre-signup trigger. Use the same configurations I've listed below.
$ amplify add auth
Do you want to use the default authentication and security configuration? Manual configuration
Select the authentication/authorization services that you want to use: User Sign-Up, Sign-In, connected with AWS IAM controls
(Enables per-user Storage features for images or other content, Analytics, and more)
Please provide a friendly name for your resource that will be used to label this category in the project: presignup9aa404bb9aa404bb
Please enter a name for your identity pool. presignup9aa404bb_identitypool_9aa404bb
Allow unauthenticated logins? (Provides scoped down permissions that you can control via AWS IAM) No
Do you want to enable 3rd party authentication providers in your identity pool? No
Please provide a name for your user pool: presignup9aa404bb_userpool_9aa404bb
Warning: you will not be able to edit these selections.
How do you want users to be able to sign in? Email and Phone Number
Do you want to add User Pool Groups? No
Do you want to add an admin queries API? No
Multifactor authentication (MFA) user login options: OPTIONAL (Individual users can use MFA)
For user login, select the MFA types: (Press <space> to select, <a> to toggle all, <i> to invert selection)SMS Text Message
Please specify an SMS authentication message: Your authentication code is {####}
Email based user registration/forgot password: Disabled (Uses SMS/TOTP as an alternative)
Please specify an SMS verification message: Your verification code is {####}
Do you want to override the default password policy for this User Pool? No
Warning: you will not be able to edit these selections.
What attributes are required for signing up?
Specify the app's refresh token expiration period (in days): 30
Do you want to specify the user attributes this app can read and write? Yes
Specify read attributes: Email, Phone Number, Preferred Username, Email Verified?, Phone Number Verified?
Specify write attributes: Email, Phone Number, Preferred Username
Do you want to enable any of the following capabilities? (Press <space> to select, <a> to toggle all, <i> to invert selection)
Do you want to use an OAuth flow? No
? Do you want to configure Lambda Triggers for Cognito? Yes
? Which triggers do you want to enable for Cognito Pre Sign-up
? What functionality do you want to use for Pre Sign-up Create your own module
Succesfully added the Lambda function locally
? Do you want to edit your custom function now? Yes
Please edit the file in your editor: Desktop/pre-signup/amplify/backend/function/presignup9aa404bb9aa404bbPreSignup/src/custom.js
? Press enter to continue
Successfully added resource presignup9aa404bb9aa404bb locally
Whooooakay, still with me π
Now we're gonna dive into the fun stuff!
In the prompt above, it will ask you if you'd like to edit your function. It will open up the custom.js file at /amplify/backend/function/<lambda-name>/custom.js
. At this point you can delete the custom file and add the following code to /amplify/backend/function/<lambda-name>/index.js
exports.handler = async (event, context, callback) => {
console.log({event}, event.request, event.request.userAttributes)
callback(null, event)
}
A little side note
With a PreSignUp trigger, we must return the original event back to Cognito after we add our custom logic. To demonstrate this, weβre going to deploy the code below with some console.logs of the event data, so you can see what to expect.
$ amplify push
While that's deploying our resources to the cloud, let's add some Amplify Auth configurations to App.js
import React from "react";
import Auth from "@aws-amplify/auth";
import { withAuthenticator } from "aws-amplify-react";
import config from "./aws-exports";
Auth.configure(config);
const App = (props) => {
return (
<div style={{ color: "white", fontSize: 13}}>
Wooohoooo, Succcessfully signed up user with username
</div>)
};
export default withAuthenticator(App, {
signUpConfig: {
signUpFields: [{ key: 'phone_number', required: false }]
}
});
After amplify push
is finished, run yarn start
You'll be presented with a SignIn screen. Click create account and sign up with the following credentials (do not fill in phone number field, fill in username as phone number)
Username: +1111111111
Password: Password1@
Email: example@example.com
If you're presented with the Confirm Sign Up page, congratulations! Our PreSignUp Lambda forwarded the event correctly and Cognito was able to continue with SignUp. Now try signing up again, but this time with the following credentials:
Username: +2222222222
Password: Password1@
Email: example@example.com
Ahhh, just as we expected. Cognito allowed us to sign up with the same email when used as an optional attribute. Cognito is only enforcing uniqueness for username, which is a phone number.
I would take this time to check the cloudwatch logs for your lambda as we'll be using the event data for something later π (hint, hint: quick way to find the logs is to find your presignup lambda inside the lambda console, click on monitoring, then view in cloudwatch)
What our logs should look like:
2020-03-02T20:37:56.850Z d740d6f7-71be-4634-a36b-23d916e1cdb9 INFO
{ event:
{ version: '1',
region: 'us-east-1',
userPoolId: 'us-east-1_HQBTO8LlF',
userName: '5f1fa3d5-acfe-4e65-80b0-7e5753c83c25',
callerContext: {
awsSdkVersion: 'aws-sdk-unknown-unknown',
clientId: '2b7j54vvm9a7c1inqum0nkq4v'
},
triggerSource: 'PreSignUp_SignUp',
request: {
userAttributes: {
phone_number: '+11111111111',
email: 'example@example.com'
},
validationData: null
},
response: {
autoConfirmUser: false,
autoVerifyEmail: false,
autoVerifyPhone: false
}
}
}
Now we've seen the problem in action, we're going to add the code for our PreSignUp lambda. It will looks for users with email that matches the one from provided from user signup. Replace the code at /amplify/backend/function/<lambda-name>/index.js
with the following:
const AWS = require('aws-sdk');
AWS.config.region = 'us-east-1';
const identity = new AWS.CognitoIdentityServiceProvider();
exports.handler = async (event, context, callback) => {
if (event.request.userAttributes.email) {
const {email} = event.request.userAttributes
const userParams = {
UserPoolId: event.userPoolId,
AttributesToGet: ['email'],
Filter: `email = \"${email}\"`,
Limit: 1,
};
try {
const {Users} = await identity.listUsers(userParams).promise();
console.log({Users})
if (Users && Users.length > 0) {
callback('EmailExistsException', null);
} else {
callback(null, event);
}
} catch (error) {
console.log({error}, JSON.stringify(error))
callback({error}, null);
}
} else {
callback('MissingParameters', null);
}
};
What's happening here is that we're making a request for all users in our user pool and filtering it down to users with the email we've provided. If a user exists, we return EmailExistsException
error message.
Protip: With Amplify, we can test our functions locally π€―
Remember those cloudwatch logs from earlier? They're about to come in handy. Copy the event data into /amplify/backend/function/<lambda-name>/events.json
{
"version": "1",
"region": "us-east-1",
"userPoolId": "us-east-1_HQBTO8LlF",
"userName": "5f1fa3d5-acfe-4e65-80b0-7e5753c83c25",
"callerContext": {
"awsSdkVersion": "aws-sdk-unknown-unknown",
"clientId": "2b7j54vvm9a7c1inqum0nkq4v"
},
"triggerSource": "PreSignUp_SignUp",
"request": {
"userAttributes": {
"phone_number": "+1111111111",
"email": "example@example.com"
},
"validationData": null
},
"response": {
"autoConfirmUser": false,
"autoVerifyEmail": false,
"autoVerifyPhone": false
}
}
Run the following from your terminal
$ amplify function invoke <your-lambda-name>
Niiiiiiiice. Although our lambda works locally, it will need permissions to call Cognito:Identity-ServiceProvider.listUsers()
when it's deployed. We're gonna step into our lambda's cloudformation template and add a Policy. Go to /amplify/backend/function/<lambda-name>/<lambda-name>-cloudformation-template.json
Nested under "Resources"
you'll see the following:
"LambdaExecutionRole": {
"Type": "AWS::IAM::Role",
...
},
"lambdaexecutionpolicy": {
"DependsOn": [
"LambdaExecutionRole"
],
"Type": "AWS::IAM::Policy",
...
}
We're going to add a policy between the existing role and policy
"LambdaExecutionRole": {
"Type": "AWS::IAM::Role",
...
},
"lambalistuserspolicy": {
"DependsOn": [
"LambdaExecutionRole"
],
"Type": "AWS::IAM::Policy",
"Properties": {
"PolicyName": "lambda-list-users-policy",
"Roles": [
{
"Ref": "LambdaExecutionRole"
}
],
"PolicyDocument": {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"cognito-idp:ListUsers"
],
"Resource": {
"Fn::Sub": [
"arn:aws:cognito-idp:${region}:${account}:userpool/us-east-1_HQBTO8LlF",
{
"region": {
"Ref": "AWS::Region"
},
"account": {
"Ref": "AWS::AccountId"
}
}
]
}
}
]
}
}
},
"lambdaexecutionpolicy": {
"DependsOn": [
"LambdaExecutionRole"
],
"Type": "AWS::IAM::Policy",
...
}
Now it's time to deploy our pride and joy π₯³
amplify function push
Let's try signing up with any username and an email of example@example:
Ohhhh, the sweet, sweet taste of a fully functioning PreSignUp lambda that enforces email uniqeuness π π π π π π
To learn more about the resources used in this walkthrough, check out the following:
Cognito List Users
Amplify Authentication
Pre Sign-up Lambda Trigger
Follow me on Twitter @andthensumm
Top comments (9)
Did you find any way to make pool id parametric?
If I'm understanding you correctly, the user pool id is automatically passed into any cognito trigger. Every trigger event will have the
userPoolId
field availableHi, I use Lambda function with React Native (Auth.signUp) and the error message returned is "PreSignUp failed with error [object Object].", can i pass the custom message only for this specific lambda like "SignUp failed, your email il already user"? I don't see EmailExistsException string... Thanks
You are a life saver. Thank you so much.
heyooo, always love that, glad it helped
Really much needed thing! Thanks a lot
Worked like a charm..thanks
Ayyyy love to hear that!
Hi, thank you for this article. But how about performance of listUsers? how slow will the request be if the app has millions of users?