Skip to content

Commit 5d3c42b

Browse files
authored
Merge pull request #2704 from proton0210/proton0210-feature-cognito-lambda-dynamodb
New serverless pattern - cognito-lambda-dynamodb
2 parents 5daa5db + 0eb46f0 commit 5d3c42b

File tree

19 files changed

+856
-0
lines changed

19 files changed

+856
-0
lines changed

cognito-lambda-dynamodb/README.md

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
# Amazon Cognito to AWS Lambda to Amazon DynamoDB
2+
3+
This pattern demonstrates how to create a user in [Amazon Cognito](https://aws.amazon.com/cognito/), handle a **Post Confirmation** trigger using an [AWS Lambda](https://aws.amazon.com/lambda/) function, and store user details in [Amazon DynamoDB](https://aws.amazon.com/dynamodb/). Specifically, when a user signs up and confirms their account in Cognito, the Lambda function automatically writes that user's information to a DynamoDB table.
4+
5+
Learn more about this pattern at Serverless Land Patterns: **<< Add the live URL here >>**
6+
7+
> **Important**: This application uses various AWS services and there are costs associated with these services after the Free Tier usage. Please see the [AWS Pricing page](https://aws.amazon.com/pricing/) for details. You are responsible for any AWS costs incurred. No warranty is implied in this example.
8+
9+
---
10+
11+
## Requirements
12+
13+
1. [Create an AWS account](https://portal.aws.amazon.com/gp/aws/developer/registration/index.html) if you do not already have one and log in. The IAM user/role that you use must have sufficient permissions to make necessary AWS service calls and manage AWS resources.
14+
2. [AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/install-cliv2.html) installed and configured.
15+
3. [Git Installed](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git).
16+
4. [Node.js](https://nodejs.org/en/download/) (10.x or higher).
17+
5. [AWS CDK Toolkit](https://docs.aws.amazon.com/cdk/latest/guide/cli.html) installed (e.g., `npm install -g aws-cdk`).
18+
6. [Docker](https://docs.docker.com/get-docker/) is recommended if you need to bundle Lambda dependencies in certain ways (though for this TypeScript example, it may not be strictly necessary).
19+
20+
---
21+
22+
## Deployment Instructions
23+
24+
1. **Clone the GitHub Repository**
25+
Create a new directory, navigate to that directory in a terminal, and clone the **serverless-patterns** GitHub repository:
26+
```bash
27+
git clone https://github.com/aws-samples/serverless-patterns.git
28+
```
29+
2. Change directory to the pattern directory:
30+
```
31+
cd serverless-patterns/cognito-lambda-dynamodb/cdk
32+
```
33+
3. Install Dependencies:
34+
35+
```
36+
npm install
37+
```
38+
39+
4. Synthesize the AWS CloudFormation Templates:
40+
41+
```
42+
cdk synth
43+
```
44+
45+
5. Deploy the Stack
46+
```
47+
cdk deploy
48+
```
49+
6. Note the Outputs
50+
51+
After deployment, CDK provides outputs such as the UserPoolId and UserPoolClientId. Make sure to save these for reference. They may be required for testing or client-side integration
52+
53+
## How it works
54+
55+
### Cognito User Pool
56+
57+
- A new Amazon Cognito User Pool is created. Users can sign up using their email address. An optional User Pool Client is also created to handle authentication flows.
58+
59+
### Post Confirmation Trigger
60+
61+
- When a user signs up and confirms their email, Cognito invokes the Post Confirmation Lambda function (AddUserPostConfirmationFunc).
62+
63+
### AWS Lambda Handler
64+
65+
- The Lambda function reads attributes from the event (such as sub [the unique user ID], email, and optional name attributes). It then inserts a new item into the DynamoDB table.
66+
67+
### DynamoDB Table
68+
69+
- A DynamoDB table named Users is created with a primary key called UserID. The Lambda function stores user data (UserID, Email, firstName, lastName, etc.) in this table with each new sign-up.
70+
71+
### Result
72+
73+
- Whenever a new user confirms their email in Cognito, an entry is automatically created in the DynamoDB table with that user's information.
74+
75+
## Testing
76+
77+
## Option 1: Manual Sign-Up through Cognito
78+
79+
1. In the Amazon Cognito Console:
80+
81+
- Navigate to **User Pools** and select the **USER-POOL** that was created.
82+
- Choose the **Users** section and manually create a new user or do a user sign-up using the **Hosted UI** or any relevant client (e.g., AWS Amplify).
83+
- After confirming the user, check the **Users** table in Amazon DynamoDB Console to see if the new record appears.
84+
85+
## Option 2: Automated Testing with Jest (E2E Tests)
86+
87+
This project includes an end-to-end test in `cdk/__tests__/e2e/confirm-user-sign-up.test.ts`. By default, the test references environment variables in `cdk/__tests__/constants.ts`. Steps:
88+
89+
1. Populate `REGION`, `USER_POOL_ID`, `CLIENT_USER_POOL_ID`, and `TABLE_NAME` in `cdk/__tests__/constants.ts` (or set them as environment variables before running tests if you prefer).
90+
2. Run:
91+
92+
```bash
93+
npm run test
94+
```
95+
96+
This will perform a sign-up flow using AWS SDK for Cognito, confirm the new user, and then query DynamoDB to validate that the user entry exists.
97+
98+
## Cleanup
99+
100+
1. Delete the stack
101+
```bash
102+
cdk destroy
103+
```
104+
1. Confirm the stack has been deleted
105+
```bash
106+
aws cloudformation list-stacks --query "StackSummaries[?contains(StackName,'STACK_NAME')].StackStatus"
107+
```
108+
109+
---
110+
111+
Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved.
112+
113+
SPDX-License-Identifier: MIT-0
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
*.js
2+
!jest.config.js
3+
*.d.ts
4+
node_modules
5+
6+
# CDK asset staging directory
7+
.cdk.staging
8+
cdk.out
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
*.ts
2+
!*.d.ts
3+
4+
# CDK asset staging directory
5+
.cdk.staging
6+
cdk.out
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# Welcome to your CDK TypeScript project
2+
3+
This is a blank project for CDK development with TypeScript.
4+
5+
The `cdk.json` file tells the CDK Toolkit how to execute your app.
6+
7+
## Useful commands
8+
9+
* `npm run build` compile typescript to js
10+
* `npm run watch` watch for changes and compile
11+
* `npm run test` perform the jest unit tests
12+
* `npx cdk deploy` deploy this stack to your default AWS account/region
13+
* `npx cdk diff` compare deployed stack with current state
14+
* `npx cdk synth` emits the synthesized CloudFormation template
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export const REGION = "";
2+
export const USER_POOL_ID = "";
3+
export const CLIENT_USER_POOL_ID = "";
4+
export const TABLE_NAME = "Users";
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import * as given from "../steps/given";
2+
import * as then from "../steps/then";
3+
import * as when from "../steps/when";
4+
5+
describe("When a user Signs up ", () => {
6+
it("Users profile should be saved in DynamoDB", async () => {
7+
const { password, given_name, family_name, email } = given.a_random_user();
8+
console.log("name: ", given_name, family_name);
9+
const userSub = await when.a_user_signs_up(
10+
password,
11+
email,
12+
given_name,
13+
family_name
14+
);
15+
16+
console.log("user: ", userSub);
17+
const ddbUser = await then.user_exists_in_UsersTable(userSub);
18+
19+
console.log("ddbUser: ", ddbUser);
20+
21+
expect(ddbUser.UserID).toMatch(userSub);
22+
});
23+
});
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
import * as cognito from "@aws-sdk/client-cognito-identity-provider";
2+
3+
import Chance from "chance";
4+
import { REGION, USER_POOL_ID, CLIENT_USER_POOL_ID } from "../constants";
5+
const cognitoClient = new cognito.CognitoIdentityProviderClient({
6+
region: REGION,
7+
});
8+
9+
const chance = new Chance();
10+
11+
const userpool = USER_POOL_ID;
12+
const userpoolClient = CLIENT_USER_POOL_ID;
13+
14+
export const a_random_user = () => {
15+
const given_name = chance.first({ nationality: "en" });
16+
const family_name = chance.first({ nationality: "en" });
17+
const password = ensurePasswordPolicy(
18+
chance.string({
19+
length: 12,
20+
pool: "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()_+",
21+
})
22+
);
23+
console.log("password: ", password);
24+
const email = `${given_name}-${family_name}@dev.com`;
25+
return { given_name, family_name, password, email };
26+
};
27+
28+
function ensurePasswordPolicy(password: string): string {
29+
let newPassword = password;
30+
if (!/[a-z]/.test(newPassword))
31+
newPassword += chance.letter({ casing: "lower" });
32+
if (!/[A-Z]/.test(newPassword))
33+
newPassword += chance.letter({ casing: "upper" });
34+
if (!/[0-9]/.test(newPassword))
35+
newPassword += chance.integer({ min: 0, max: 9 }).toString();
36+
if (!/[!@#$%^&*()_+]/.test(newPassword))
37+
newPassword += chance.pickone([
38+
"!",
39+
"@",
40+
"#",
41+
"$",
42+
"%",
43+
"^",
44+
"&",
45+
"*",
46+
"(",
47+
")",
48+
"_",
49+
"+",
50+
]);
51+
return newPassword; // Ensure the password is still 12 characters long
52+
}
53+
54+
export const an_authenticated_user = async (): Promise<any> => {
55+
const { given_name, family_name, email, password } = a_random_user();
56+
57+
const userPoolId = userpool;
58+
const clientId = userpoolClient;
59+
console.log("userPoolId", userPoolId);
60+
console.log("clientId", clientId);
61+
62+
console.log(`[${email}] - signing up...`);
63+
64+
const command = new cognito.SignUpCommand({
65+
ClientId: clientId,
66+
Username: email,
67+
Password: password,
68+
UserAttributes: [
69+
{ Name: "firstName", Value: given_name },
70+
{
71+
Name: "lastName",
72+
Value: family_name,
73+
},
74+
],
75+
});
76+
77+
const signUpResponse = await cognitoClient.send(command);
78+
const userSub = signUpResponse.UserSub;
79+
80+
console.log(`${userSub} - confirming sign up`);
81+
82+
const adminCommand: cognito.AdminConfirmSignUpCommandInput = {
83+
UserPoolId: userPoolId as string,
84+
Username: userSub as string,
85+
};
86+
87+
await cognitoClient.send(new cognito.AdminConfirmSignUpCommand(adminCommand));
88+
89+
console.log(`[${email}] - confirmed sign up`);
90+
91+
const authRequest: cognito.InitiateAuthCommandInput = {
92+
ClientId: process.env.CLIENT_USER_POOL_ID as string,
93+
AuthFlow: "USER_PASSWORD_AUTH",
94+
AuthParameters: {
95+
USERNAME: email,
96+
PASSWORD: password,
97+
},
98+
};
99+
100+
const authResponse = await cognitoClient.send(
101+
new cognito.InitiateAuthCommand(authRequest)
102+
);
103+
104+
console.log(`${email} - signed in`);
105+
106+
return {
107+
username: userSub as string,
108+
name: `${given_name} ${family_name}`,
109+
email,
110+
idToken: authResponse.AuthenticationResult?.IdToken as string,
111+
accessToken: authResponse.AuthenticationResult?.AccessToken as string,
112+
};
113+
};
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import {
2+
DynamoDBClient,
3+
GetItemCommand,
4+
ScanCommand,
5+
} from "@aws-sdk/client-dynamodb";
6+
import { unmarshall, marshall } from "@aws-sdk/util-dynamodb";
7+
import { REGION, TABLE_NAME } from "../constants";
8+
const ddbClient = new DynamoDBClient({ region: REGION });
9+
export const user_exists_in_UsersTable = async (
10+
userSub: string
11+
): Promise<any> => {
12+
let Item: unknown;
13+
console.log(`looking for user [${userSub}] in table [${TABLE_NAME}]`);
14+
15+
// search for UserID in dynamoDb] Get Item Command
16+
const getItemCommand = new GetItemCommand({
17+
TableName: TABLE_NAME,
18+
Key: {
19+
UserID: { S: userSub },
20+
},
21+
});
22+
23+
const getItemResponse = await ddbClient.send(getItemCommand);
24+
console.log("Get Item Command Response ....", getItemResponse);
25+
26+
if (getItemResponse.Item) {
27+
Item = unmarshall(getItemResponse.Item); // Get the first matching item
28+
}
29+
30+
console.log("found item:", Item);
31+
expect(Item).toBeTruthy();
32+
return Item;
33+
};
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import * as cognito from "@aws-sdk/client-cognito-identity-provider";
2+
import { REGION, USER_POOL_ID, CLIENT_USER_POOL_ID } from "../constants";
3+
4+
const cognitoClient = new cognito.CognitoIdentityProviderClient({
5+
region: REGION,
6+
});
7+
8+
const userpool = USER_POOL_ID;
9+
const userpoolClient = CLIENT_USER_POOL_ID;
10+
11+
export const a_user_signs_up = async (
12+
password: string,
13+
email: string,
14+
given_name: string,
15+
family_name: string
16+
): Promise<string> => {
17+
const userPoolId = userpool;
18+
const clientId = userpoolClient;
19+
const username = email;
20+
21+
console.log(`[${email}] - signing up...`);
22+
23+
const command = new cognito.SignUpCommand({
24+
ClientId: clientId,
25+
Username: username,
26+
Password: password,
27+
UserAttributes: [
28+
{ Name: "email", Value: email },
29+
{ Name: "custom:firstName", Value: given_name },
30+
{ Name: "custom:lastName", Value: family_name },
31+
],
32+
});
33+
34+
const signUpResponse = await cognitoClient.send(command);
35+
const userSub = signUpResponse.UserSub;
36+
37+
const adminCommand: cognito.AdminConfirmSignUpCommandInput = {
38+
UserPoolId: userPoolId as string,
39+
Username: userSub as string,
40+
};
41+
42+
const result = await cognitoClient.send(
43+
new cognito.AdminConfirmSignUpCommand(adminCommand)
44+
);
45+
46+
console.log("CONFIRM SIGNUP RESPONSE", result);
47+
48+
console.log(`[${email}] - confirmed sign up`);
49+
50+
return userSub as string;
51+
};
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
#!/usr/bin/env node
2+
import * as cdk from "aws-cdk-lib";
3+
import { CdkStack } from "../lib/cdk-stack";
4+
5+
const app = new cdk.App();
6+
new CdkStack(app, "UserManagementStack");

0 commit comments

Comments
 (0)