Nowadays, almost every application on the web needs authentication and user management mechanisms. No matter the purpose of an application, users should have personal accounts in order to be identified in the system and receive access to services.

From a developer’s perspective, authentication is pretty similar from app to app and usually consists of a database with users, sign-in & sign-up forms, and an API for identity verification (for example with password). Other items such as email confirmation and password reset should also be implemented.

The good news is that there is no need to develop all the parts each time you start building a new app. Utilizing third-party cloud services instead of implementing your own solutions can help you save time and money during development. Also, such practices deliver results that are usually much more reliable, especially if you are implementing services from a mature third-party provider like Amazon Web Services.

AWS brings hundreds of tools for various purposes, including for our topic today: Implementing reliable sign-up for an app using AWS Cognito and extended functionality with AWS Lambda and SES.

Prerequisites

First of all, to complete this task you will need an AWS account. In case you haven’t created one yet, please navigate to AWS Free Tier page and sign up. The next important thing is to install Node.JS on your machine after which you can open the terminal and type the command:

npm install -g serverless

This will install Serverless Framework, which is very powerful and the most popular toolkit for building serverless applications. We will need this tool later on. The last thing, for now, is to run another command:

serverless config credentials --provider aws --key xxxxxxxxxxxxxx --secret xxxxxxxxxxxxxx

The hidden part (xxxxxxxxxxxxxx)is a key pair from your AWS account. These keys are a combination of an access key and a secret key used to login to your AWS account if you are using AWS CLI or SDK. The key pair is also necessary because Serverless Framework needs program access to the AWS account. You can easily find the keys using this guide.

AWS Cognito Setup

AWS Cognito is a great service brought to you by Amazon. Its main goal is to make developers as free as possible by taking care of the sign-in/sign-out logic, secure access management, and other common items related to application users.

To use this tool, you first must create a user pool, a directory for users of the future application.

Navigate to the user pool creation page, type the name of your future user pool, and press “review defaults.” A user pool can be configured by way of numerous different settings, most of which are beyond the scope of this guide. So let’s just go to the “Attributes” section and make some changes there.

For simplicity’s sake, let’s establish that your users should use an email address as their username for sign-up or login. To do that, select “allow email addresses.”

AWS Cognito

AWS Cognito Configuration

Attributes are the list of fields that can appear on a user object, some of which may be marked as “Required.” You will probably want to mark “email” and “name” attributes as required, meaning those fields can not be left empty upon user sign-up.

AWS Cognito

AWS Cognito Attributes

AWS Cognito provides support for a bunch of standard attributes, but it also allows for custom attributes if your application needs any special fields to store. When you are finished with all required and desired attributes, simply save your settings to finish the creation process.

Now you need to set up the frontend to make it possible for users to sign up for your application. Before switching to client-side code, we need to create an App client to be able to identify your application during future calls from the frontend to Cognito. So, go to the “App clients” section and click on the “add an app client” link:

AWS Cognito

AWS Cognito – App clients

Now type the name you want, uncheck all boxes (as we do not need all of those options in this guide), and press “create.”

AWS Cognito

AWS Cognito App Clients configuration

After creating the client, you can see the client ID, which will be needed shortly.

FrontEnd Implementation

One of the most common ways of implementing interaction between an app’s frontend and user pool is by using one of the SDKs provided by AWS for web and mobile apps. In this guide, Identity SDK will be used, so you’ll need to add it to your application web page via one of the methods described in these docs. Follow the instructions to set up a basic web page with the SDK added and ready to use.

Now, you can create a simple sign-up form in the body tag of index.html file:

<form style="width: 200px;margin: 0 auto;" id="sign-up-form">
    <h2>Sign Up</h2>
    <p>Name</p>
    <input type="text" name="name">
    <p>Email</p>
    <input type="text" name="email">
    <p>Password</p>
    <input type="password" name="password">
    <br>
    <button>Sign up</button>
</form>

<script src="app.js"></script>

The form itself is pretty simple. Go ahead and create the app.js file and put the following code there:

const poolData = {
    UserPoolId: 'YOUR_USER_POOL_ID',  // get in Cognito console
    ClientId: 'YOUR_CLIENT_ID'  // cognito console -> App clients
}

// create an instance of User Pool
const userPool = new AmazonCognitoIdentity.CognitoUserPool(poolData);

const form = document.querySelector('#sign-up-form'); // put the form into variable
form.addEventListener('submit', function(event) {
    event.preventDefault(); // prevent unnecessary navigate on submit
    // get the form data as a plain object
    const formData = Object.fromEntries(new FormData(form).entries());
    signUp(formData);
});

function signUp(formData) {
    // attributes that should be placed onto user object, only name in your case
    const attributes = [
        { Name: 'name', Value: formData.name }
    ]
    userPool.signUp(formData.email, formData.password, attributes, null, onSignUp);
}

function onSignUp(err, userData) {
    if (err) {
        alert (JSON.stringify(err)); // for example if user already exists
    } else {
        console.log(userData); // good, user was successfully created
    }
}

Now open the web page, fill in the fields, and submit the form.

Great! You’ve just signed up for your application! However, if you navigate to the Users list in the Cognito console, you will notice that you have an “UNCONFIRMED” status as a new user.

cognito unconfirmed

New User UNCONFIRMED Status

No worries. AWS Cognito enables user confirmation by default, which is pretty cool! The confirmation code should have already been sent to the email address you used to sign up.

Cognito Confirmation Code

AWS Cognito Confirmation Code

From a code perspective, this SDK is great because it provides default methods for almost all basic things such as user confirmation as well as sign in, sign up, and password reset. Now, let’s add confirmUser function in the app.js file and call it on a successful sign up:

function signUp(formData) {
    const attributes = [
        { Name: 'name', Value: formData.name }
    ]
    userPool.signUp(formData.email, formData.password, attributes, null, onSignUp);
}

function onSignUp(err, userData) {
    if (err) {
        return alert (JSON.stringify(err));
    } else {
        // instead of logging placed here before, call newly created function
        confirmUser(userData.user);
    }
}

function confirmUser(user) {
    // just a prompt for simplicity
    const confirmCode = prompt('Confirmation code:');
    // user here is an instance of CognitoUser, so it already inherits necessary method
    user.confirmRegistration(confirmCode, true, onConfirmed);
}

function onConfirmed(err) {
    if (err) {
        return alert (JSON.stringify(err));
    } else {
        alert('Success');
    }
}

Now, after a user submits the form, a confirmation prompt will appear asking for the code sent via email.

AWS Cognito

AWS Cognito Confirmation Prompt

After confirmation, the status of the user will change to “CONFIRMED.”

AWS Cognito confirmed

New User CONFIRMED Status

Expand Messaging Functionality

AWS Cognito provides functionality for verification and invitation emails right out of the box. You can even customize the text and subject of an email. But in real life, sometimes you’ll need to send alternative email messages to users, for example, a greeting message to a new user upon successful registration or notification to the administrator about a new user in the system. So how can you, say, implement the administrator notification?

You will need to run some code to send an email each time a new user is confirmed in the system. And there are perfect tools to do just that!

AWS Lambda

AWS Lambda is a service designed for executing code in response to different events happening across your application. You are probably already familiar with this service or have already read our serverless guide on the topic. In any event, AWS Lambda suits your case! Moreover, AWS Cognito supports a bunch of different triggers related to various events happening in the user pool. A Post-Confirmation trigger should be perfect for a custom notification message to alert the administrator about a new user. But before coding the final solution, you still need to decide how you are going to send emails.

AWS SES

Simple Email Service is a reliable and cost-effective service delivered by Amazon and designed for handling email messaging in applications of any scale and purpose. Here, you’ll use AWS SES in your application for sending notification welcome emails. But before that, you need to verify at least one email to be able to send messages. To do this, navigate to the SES Console, press the “Verify a New Email Address” button, and follow the simple instructions to verify an email.

AWS SES

AWS SES Email Verification

Note: By default, SES works in the sandbox mode, meaning that you can send emails only from and to verified addresses. Sandbox mode is more than enough for your implementation today, as we are going to send a message only to the application administrator.

Serverless Implementation

At the beginning of this article, you were asked to set up and configure Serverless Framework. If you have done everything correctly, you should be able to now run further commands without any trouble.

Create a folder, open a terminal from that folder, and execute:

serverless create --template aws-nodejs

After that, files of the basic AWS Lambda function boilerplate should appear. The serverless.yml file stores the configuration of the AWS Lambda function; you can find out more information about the settings here. The handler.js file is the main script file where we are going to write the code of your notification sender.

Let’s start with writing proper configs into the serverless.yml file:

service: confirm-notifier # name of your service

provider:
name: aws
runtime: nodejs10.x
region: eu-west-1
iamRoleStatements: # this is important to allow sending emails from AWS Lambda function
- Effect: Allow
Action:
- ses:SendEmail
Resource: "*"

functions:
newUserNotify: # name of the function
handler: handler.newUserNotify  # name of the function in handler.js
events:
- cognitoUserPool:
pool: my-app-user-pool     # name of user pool you created
trigger: PostConfirmation  # name of the Cognito trigger
existing: true   #we are connecting to existing user pool

Here is the heart of your notifier, handler.js code:

const aws = require('aws-sdk');
const ses = new aws.SES();
const ADM_EMAIL = 'admin_email@verifiedmail.com'; // verified receiver’s email
const NO_REPLY_EMAIL = 'some_email@verifiedmail.com'; // verified sender’s email

// main handler function
module.exports.newUserNotify = async (event) => {
    const attributes = event.request.userAttributes;  // read user attributes from an event
    await sendNotification(attributes.name, attributes.email);
    return event; // pass event object back, as Cognito expects it to be returned
};

function sendNotification(userName, userEmail) {
    const notificationText = `
    New user ${userName} with email: ${userEmail} successfully registered!
    `

    return new Promise((resolve, reject) => {
        const params = {
            Destination: {
                ToAddresses: [ADM_EMAIL]
            },
            Message: {
                Body: {
                    Text: {
                        Data: notificationText
                    }
                },
                Subject: {
                    Data: "New User confirmed!"
                }
            },
            Source: NO_REPLY_EMAIL
        };

        ses.sendEmail(params, (err, data) => {
            if (err) {
                reject(err);
            } else {
                resolve(data);
            }
        });
    });
}

Now try to sign up and confirm a new user. Then log in to the admin’s email you mentioned in the code above, and you should hopefully see this in your inbox:

AWS Lambda

User Confirmation Email

AWS Lambda Observability

Even though this application consists of only one Lambda function, it’s still good to have a complete picture of its performance, successful and failed executions, cost, etc. Fortunately, it is very easy to achieve this using Epsagon one-click monitoring.

Navigate to https://dashboard.epsagon.com/login, and create an account if you don’t have one already. Then simply press the big blue button.

AWS monitoring

AWS Monitoring by Epsagon

You will be redirected to the AWS CloudFormation. Scroll down, check the box, and confirm the creation of a premade CloudFormation stack. Now, wait for the stack to be created, which can take a few minutes. The premade stack includes a single role with read-only permissions, so you should not worry about the security of your AWS resources.

Now get back to the tab where you clicked the “Connect Epsagon” button or just navigate to https://dashboard.epsagon.com/get_started/instrumentation. There, you will see the list of your Lambda functions. Choose those you would like to monitor (probably all).

From that point, you can track all invocations of your functions and see the trace and details of each execution.

Lambda SES

Epsagon Architecture View

And that’s it. You have successfully set up Epsagon monitoring for your serverless functions!

Epsagon Dashboard

Epsagon Dashboard

Start your free trial

Summary

We have implemented basic sign-up functionality for regular web applications with minimum codebase and time spent. We used the AWS Cognito service to create a user directory for the app and Identity SDK on the client-side for communicating with the service. With this basic knowledge, you can now continue implementing any of the common scenarios for web and mobile applications

We also managed to extend default Cognito functionality with custom notifications using AWS Lambda and SES. The combination of Cognito triggers and AWS Lambda opens up many possibilities for implementing various scenarios as well.

Finally, we learned that Serverless Framework is a great tool for deploying serverless applications and that Epsagon monitoring is the best system for serverless observability.

Check out our latest blog posts:

Retail at the Scale of Serverless with AWS

Distributed Tracing Through RabbitMQ Using Node.js & Jaeger

Distribute Messages Between Java Microservices Using Kafka

Announcing: Epsagon, First Provider of Distributed Tracing for AWS AppSync

AWS EventBridge and Epsagon Automated Tracing Integrate