DEV Community

Christian Nwamba
Christian Nwamba

Posted on • Edited on

Automatically Create a DynamoDB Record from a Cognito User Pool

To successfully authenticate a user, it means that you have securely stored their credentials on your server, and you verify their accuracy every time they try to sign in.

However, authentication is one of many reasons we store user data. In addition to verifying user credentials, creating a user often involves capturing and storing their profile information. You might want to store user data so you can use the profile data and connect it to the user data, for example, in social media use cases, such as displaying comments from all users related to a specific post.

AWS Amplify Auth uses Amazon Cognito behind the scene which means that users are stored in an AWS Cognito User Pool. This pool is different from your GraphQL API data which is stored in your DynamoDB. If you are using GraphQL API with AppSync then your user data and your app data are in two different locations.

To connect your user data from Cognito User Pool to your Amplify GraphQL API, you need to setup a Lambda function that Cognito will call with the user data. This Lambda function is a lambda trigger that is automatically invoked right after the sign-up process is completed.

In this article, we will walk through how to set up this Lambda function and connect and write to our database after Cognito has called it.

Prerequisites

To follow this tutorial, you'll need:

  1. An AWS account. If you don't have one, you can create an account here.
  2. Node.js v16.x or later and npm v6.14.4 or later

This tutorial also assumes that you're familiar with the basics of both JavaScript/ES6 and React.

Set up Your Development Environment

You need to configure the Amplify CLI locally, and connect it to your AWS account. Run the following command in your terminal:



npm install -g @aws-amplify/cli


Enter fullscreen mode Exit fullscreen mode

Next, run the amplify configure command to configure your AWS Amplify environment. Follow the prompts to create a new IAM user that AWS Amplify will use to manage resources for your app. Here’s a link with step-by-step details on how to configure the Amplify CLI.

Create an AWS Amplify Project

The Amplify Console is where you build, manage, and host your Amplify applications. Let's set up an AWS Amplify app. Log into your AWS account and search for AWS Amplify in the console. Select AWS Amplify to open the Amplify Console.

If this is your first app, scroll to the bottom of the page in the Amplify Studio section, select Get started. Name your app, and select Confirm Deployment.

If you’ve created an Amplify App in the past, follow the steps below:

  • Select New app in the upper right-hand corner, and select Build an app from the dropdown menu.

  • Name your app, and select Confirm Deployment.

This would take a few seconds as Amplify need some time to set up a new project. Once that is completed, click the Launch Studio button.

AWS Amplify Studio is a visual development environment tailored for building fullstack web and mobile apps. One of its standout features is the ease with which you can set up backend resources for various tasks, such as authentication and managing customer data.

It also has a Content Management System (CMS), which is incredibly useful for viewing and managing user data.

You should now see the Home menu for your application. To learn more, see the Amplify Studio introduction.

Set up Authentication

We need to set up authentication for our app so that users can create accounts and sign in. We want only authenticated users to access the App’s UI.

Amplify simplifies authentication and authorization. You can setup a complete auth flow and auth UI for your app in minutes.

  1. Select the Authentication option in the setup menu on the screen's left side. Leave Email as the log in option.
  2. Scroll down to the Configure Signup settings section. You should see the Password protection settings in this section. Click on this to see a variety of security options for user passwords.
  3. Click the Deploy button, acknowledge the warning, and select Confirm Deployment.

You should see the progress of this deployment process — a visual representation of how your setup is being deployed in the AWS environment.

Amplify will provision AWS Cognito behind the scene. Once it’s done, you should get a confirmation message.

Create a Data Model in Amplify Studio

Next, we need to define a data model to store the user details. Amplify provides the ability to create data models, which represent the structure of the data your application will work with. The Studio data model designer provides a visual way to achieve this.

When you define your data models, AWS Amplify creates corresponding AWS AppSync GraphQL APIs to allow your application to interact with the data and Amazon DynamoDB tables based on those models.

Follow the steps below to create a data model for your app:

  • On the Setup menu on the left, select Data, and select Add model.

  • As seen in the image below, we call our data model User for this example. Click the Add a field link, and in the field that appears, type in "email".

  • For this email field, you will notice an option labeled 'is required' to the right. Check this box to make this a mandatory field.

  • Select the Save and Deploy button. Acknowledge the warning, and select Deploy.

The deployment should take a few minutes, but once it is completed, you should see a message saying you've successfully deployed the data model.

Additionally, you should also see a "pull" command that should be executed in your local terminal, precisely from your project's root directory to pull the project into your React project. You can either copy the command or ignore it for now; we'll use it soon. Let’s go ahead and set up our React project.

Set up React Project

Run the following command from your preferred directory to set up a React project:



npx create-react-app dyneapp


Enter fullscreen mode Exit fullscreen mode

Run the following command to navigate into the dyneapp directory, and start up your development server:



cd dyneapp
npm start


Enter fullscreen mode Exit fullscreen mode

You should see the running app by navigating to http://localhost:3000.

Install Amplify Libraries

The first step to using Amplify in the client is to install the necessary dependencies.

Run the following command to install the Amplify Libraries:



npm install aws-amplify @aws-amplify/ui-react


Enter fullscreen mode Exit fullscreen mode

This command installs two libraries. The aws-amplify package is the library that enables you to connect to your backend resources. The @aws-amplify/ui-react package includes React-specific UI components that you can use to build your app UI.

The @aws-amplify/ui-react library exports a connected component that handles Auth UI flow for us.

Pull Amplify Backend Project

Navigate to your Amplify Studio application, and copy the pull command displayed. This command is important as it allows you to pull the newly created Amplify backend project from the cloud to your local environment (React project in our case).

Dependency: Make sure you have AmplifyCLI configured before executing the pull command.

From your projects directory, paste the copied command into the terminal and then execute it. A typical instance of this command would appear as follows:



amplify pull --appId d2act21onzf92o --envName staging


Enter fullscreen mode Exit fullscreen mode

When you execute the command, you will be automatically redirected to your web browser to grant authorization. Once there, click 'Yes' to authenticate with Amplify Studio.

After successful login, return to the CLI. Here, you will be asked a series of questions to gather essential details about your project's configuration. Your answers will help Amplify understand your project's structure and needs.

Accept the default values highlighted below:

The Amplify CLI will automatically carry out the following steps for you:

  1. Amplify will create a new project in the current local directory. This is indicated by the creation of an amplify folder in your project's root directory, which contains all of your project's backend infrastructure settings.
  2. It creates a file called aws-exports.js in the src directory of your React application. This file holds all the configuration for the services you create with Amplify, allowing the Amplify client to access necessary information about your backend services, such as API endpoints, authentication configuration, etc.

Configure Amplify

For your application to talk to AWS, you need to configure AWS Amplify for use throughout the project. In your app's entry point index.js, import and load the configuration file:



import React from "react";
import ReactDOM from "react-dom/client";
import "./index.css";
import App from "./App";

// Add these lines
import { Amplify } from "aws-amplify";
import awsconfig from "./aws-exports";
Amplify.configure(awsconfig);

const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);


Enter fullscreen mode Exit fullscreen mode

In the code above, we import the Amplify library and configuration details from the aws-exports.js file.

We then configure AWS Amplify with the details we imported. This setup ensures that the Amplify library knows about your specific backend setup and can interact with it correctly.

Protect Your App UI with Amplify Auth

With the configuration out of the way, we can go ahead and protect our app with the Authenticator UI component. Open your src/App.js file and import the withAuthenticator component below the last import.



import "@aws-amplify/ui-react/styles.css";
import { withAuthenticator } from "@aws-amplify/ui-react";


Enter fullscreen mode Exit fullscreen mode

Next, update the code in your **src/App.js** file to match the following:



import React from "react";
//...

function App({ user, signOut }) {
  return (
    <div>
      <div className="flex justify-end px-4 py-2">
        <button
          type="button"
          className="relative inline-flex items-center gap-x-1.5 rounded-md bg-indigo-500 px-3 py-2 text-sm font-semibold text-white"
          onClick={() => signOut()}
        >
          Sign Out
        </button>
      </div>
      <div className="flex justify-center items-center h-screen w-full">
        Hello World
      </div>
    </div>
  );
}

export default withAuthenticator(App);


Enter fullscreen mode Exit fullscreen mode

The App component is a simple React component that displays a greeting message and a sign-out button. It receives signOut and user props because we wrapped the App component with a higher order compnent. The signOut function is used to log the user out of the application, and user contains the details of the currently logged-in user.

Head to the browser and you should see a sign in and create account form.

No need to create an account at the moment as we are yet to configure Lambda triggers for the authentication events.

Create a Pre-Signup Lambda Trigger

We can use Lambda triggers to intercept the auth flow and run custom code before a user is created. We need to configure Amplify Auth module with Lambda functions to extend its functionality.

Run the following command from your terminal:



amplify update auth


Enter fullscreen mode Exit fullscreen mode

This command allows us to modify our existing authentication configurations. Select Walkthrough all the auth configurations and accept the configuration for the rest of the CLI prompts as as highlighted in the image below:

Let’s go through some of the important values we selected above and why:

  • Walkthrough all the auth configurations:This option allows us to go through and modify all the configurations for your authentication resource step by step. It is helpful for changing multiple aspects or reviewing all settings.
  • Selecting to Configure Lambda Triggers for Cognito: This allows us to specify that we want to run custom code during various stages of the authentication process. Lambda triggers can be used for custom validation, adding custom logic, etc.
  • Selecting "pre-signup": With this, we are choosing to create a Lambda function that is triggered before Amazon Cognito signs up a new user.
  • Selecting "Create your own module": allows us to write our custom Lambda function to implement specific logic rather than using pre-built templates provided by Amplify.

After these steps, Amplify will create a new directory called amplify/backend/function containing the source code for your new Lambda function. The file for the custom function — custom.js will be automatically opened in VS Code for you. We will modify this code to implement our custom logic for the pre-signup.

Next, we need to cd into the amplify/backend/function/DyneappPreSignup/src folder and run the following command to install version 2 of the node-fetch module as a dependency to just the Lambda function.



npm install node-fetch@2


Enter fullscreen mode Exit fullscreen mode

Because we will be writing functions with CommonJS, we are installing version 2 of node-fetch. We need this to be able to access the Fetch API in our Node.js project to make a HTTP request to our GraphQL API.

Next, we need to add our GraphQL API credentials as environmental variables to our existing function.
Run the following command at the root of your project:



amplify update function


Enter fullscreen mode Exit fullscreen mode

Select the Lambda function you want to update and follow the CLI prompts by selecting the values displayed in the image below:

At the end of this command you can take note of your new API endpoint and API key.

Finally, we can use this GraphQL API access credentials to store the user signing up to our Amplify Datastore.

Replace the code in your custom.js file withe the following:



/**
 * @type {import('@types/aws-lambda').APIGatewayProxyHandler}
 */

const fetch = require("node-fetch");

exports.handler = async (event, context) => {
  const GRAPHQL_ENDPOINT = process.env.ADD_YOUR_API_ENDPOINT;
  const GRAPHQL_API_KEY = process.env.ADD_YOUR_API_KEY;

  const query = /* GraphQL */ `
    mutation CREATE_USER($input: CreateUserInput!) {
      createUser(input: $input) {
        email
      }
    }
  `;

  const variables = {
    input: {
      email: event.request.userAttributes.email,
    },
  };

  const options = {
    method: "POST",
    headers: {
      "x-api-key": GRAPHQL_API_KEY,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ query, variables }),
  };

  const response = {};

  try {
    const res = await fetch(GRAPHQL_ENDPOINT, options);
    response.data = await res.json();
    if (response.data.errors) response.statusCode = 400;
  } catch (error) {
    response.statusCode = 400;
    response.body = {
      errors: [
        {
          message: error.message,
          stack: error.stack,
        },
      ],
    };
  }

  return {
    ...response,
    body: JSON.stringify(response.body),
  };
};


Enter fullscreen mode Exit fullscreen mode

The code defines an AWS Lambda function handler. The function is an async function that receives a parameter — event, which contains information about the event that triggered the function. We defined two constants, GRAPHQL_ENDPOINT, and GRAPHQL_API_KEY. Replace their values with your API endpoint and API key credentials

Next, we defined a query constant that contains a GraphQL mutation that creates a user. It accepts an input of type CreateUserInput and returns the email field. We also created an object called variables that holds the input values for the GraphQL mutation. In this case, it sets the email field to the email value extracted from the userAttributes property of the event.request object. This property is set when Cognito calls this Lambda function.

The options object defines the options for the HTTP request. It specifies the request method as POST, sets the headers (including the API key), and converts the query and variables objects into a JSON string for the request body. We also initialized a response object as an empty object which will store the response from the GraphQL server.

We then try to make an HTTP request to the GraphQL endpoint. The request is sent to the GRAPHQL_ENDPOINT with the defined options. If the request is successful, the response is parsed as JSON and assigned to response.data. If the response contains errors, the status code is set to 400.

Deploy and Test the Signup Process

Run the following command at the root of your project to deploy the project and apply the changes to your backend infrastructure.



amplify push


Enter fullscreen mode Exit fullscreen mode

Amplify will display the detected changes and request your confirmation to proceed with the deployment. You can review the changes and enter "Y" to confirm. It will go ahead to create or update the necessary AWS Lambda resources and configurations based on your code.

Let’s go ahead and test the signup process by creating sample user accounts to ensure the Lambda function is triggered when a user signs up, and records are successfully created in DynamoDB. Head over to your browser, and you should see a default sign-in/sign-up UI for users that are not authenticated. Use this form to create a new account.

This function should be triggered before verification since it is pre-signup. If you head back to the Amplify Studio Console for you app, on the Setup menu on the left, select Content. You should see the user's email as shown below.

Monitor CloudWatch Logs

Debugging is crucial to understanding what is happening to your Lambda function. You can use CloudWatch Logs to inspect what is going on.

Open your Amplify Management Console, search for and select Lambda as shown below.

Search for the name of your app, and select the preSignup option. I called mine Dyneapp.

Click on the function.

Click on Monitor and then click View cloudWatch logs.

You should see a log stream, representing the invocation of the Lambda function or trigger event.

Click on the stream to view the detailed log messages for that particular function invocation.

Clean Up

To ensure that you don’t have any unused resources in you AWS account, run the following command to delete all the resources that were created in this project if you don’t intend to keep them.



amplify delete

Enter fullscreen mode Exit fullscreen mode




Conclusion

In this article, we leveraged the power of AWS Cognito Pre Sign Up Lambda Trigger to connect the AWS Cognito User Pool with AWS Amplify database. You now have the knowledge and tools to extend the capabilities of your application beyond simple authentication.

Top comments (3)

Collapse
 
vorant94 profile image
Mordechai Dror • Edited

this is the solution we followed at our project (mostly not because of app data is split between two data sources, but because of cognito's lack of flexibility) and it works pretty fine

but now the question is how to reduce the boilerplate of having one entity partially stored in two different places? like is there a way to automate cognito user update (for example email or phone) with dynamodb user update?

first thing that comes into my mind is the dynamodb streams... but since dynamodb lacks the support for unique fields it can cause problems as the data of dynamodb user is less guarded, so not every update of it can be blindly propagated to cognito...

Collapse
 
olivierleplus profile image
Olivier Leplus

Very good article, thank you!
I am using this to store profile informations. So I have a username (ID generated by cognito) as a key to match with my table.
Is there any way when a user logi to automatically retrieve the informations from this table or should I fetch them in my main component when I have a "signIn" event?

Collapse
 
jeremyburton profile image
Jeremy Burton

Thanks for this very helpful post. How would I modify the Lambda function to use AWS_IAM for auth instead of an API_KEY? I don't use an API Key for my API.