Custom authentication using AWS Cognito

Guzman Monne
9 min readJun 25, 2017

--

Most web applications need some sort of custom authentication method to handle using logins. Creating an authentication framework from scratch is not a simple task, if we want to take into account all the security details to protect our users. Many vendors on the web have realize this, and they have built services that simplify this task. On this article I will talk about AWS Cognito User Pools, a service that offers a very robust authentication method for web and mobile apps. I'll create a web application example on this article, but you can find examples on their documentation site that explains how to use this service on Android or iOS.

AWS Cognito at https://aws.amazon.com/es/cognito/

Project setup

I want to concentrate on showing how we can add Cognito into any type of JavaScript application, so I won't use any popular web application framework. So, first we'll set our project using yarn, and we'll install http-server to serve our static files.

mkdir cognito-auth
cd cognito-auth
yarn init
yarn add live-server --dev
git init
touch index.html
touch .gitignore
mkdir javascript
mkdir css

Using those commands we have created a new project folder, initialized a package.json file, added live-server as a dependency, and created a basic structure inside the project. To finish the setup we just need to modify .gitignore so git will stop following the node_modules folder, and setup our index.html page to start writing some code. For example something like this:

Now, since we have added the live-server package as a local dependency, we will create a task inside the package.json file, to run it.

{
...
"scripts": {
"serve": "live-server ."
}
...
}

Running yarn serve from the console, will start our server on 127.0.0.1:8080 .

Now, let's add a simple login form.

You can got to the repo to get the CSS code I used during this example. Is very basic so you can just use something else.

Creating a Cognito User Pool

Before we can start using AWS Cognito, we need to create a new "User Pool". Inside the "User Pool" we'll manage all the settings of how the users will log into our applications, and it will hold the database of all users. We can the create different applications that can interact with this "User Pool", each with it's own privileges.

For this example we'll use the default config, but let's review what how we could configure the "User Pool". If you want to follow along, you are going to need an AWS Account.

Go to the main service page from the AWS Management console, and click the button to start the process of creating a new "User Pool". We start by configuring its name.

Cognito "User Pool" first wizard step.

On the next step we define which information we'll require from our users in order for them to sign up. We can also choose which information we are going to use as username, and if we are going to let them use other fields as log in alias.

Cognito standard attributes configuration.

If those attributes are not enough for your application, we can configure custom attributes. They will be managed by Cognito for ourselves.

Cognito custom attributes configuration.

Next, we configure the password policy we'll enforce our users to use, if we'll allow them to sign up themselves to the application, and the expiration time of admin created accounts.

The next step is one of my favourites. We can configure Multi-Factor-Authentication, and Email and SMS verification from one page. You don't have to decide just yet if you want to configure it, you can configure it later.

Cognito MFA configuration page.

Cognito will manage for ourselves all the authentication flow notification emails. On this page we can configure the text to be shown on each message.

Cognito Email message customization page.

Before our application can interact with our "User Pool" we must define it. For JavaScript applications deselect the "Generate client secret" option. This option should only be checked if you plan to use this from an iOS or Android application. The other options are used to customize the authentication flow. We don't need them at the moment.

Cognito app configuration page.

As I mentioned before, we can configure as many applications as we need, each with its own read/write permissions.

Cognito app attribute configuration page.

This is another very cool feature of cognito. If the default authentication workflow provided by Cognito is not enough for our application, Cognito offers us several triggers which allows us to add our own custom code through Lambda functions.

Cognito workflow trigger configuration.

On the last page of the wizard form we are presented with the initial configuration of the "User Pool". You can modify this configuration after finishing the wizard without problems.

Cognito review configuration page.

After the user pool is created we can go into the "app" we created inside the "User Pool" to get its "App client id". We are going to need it to interact with the user pool from our app.

Amazon Cognito Identity SDK for JavaScript

To easily start using our new "User Pool" we are going to use the "Amazon Cognito Identity SDK for JavaScript". Here you can the repo where it's hosted.

As their README says there are many ways to add the SDK to our application. To keep it simple we'll add it, and all its dependencies, as different files.

<script src="/javascript/lib/aws-cognito-sdk.min.js"></script>
<script src="/javascript/lib/amazon-cognito-identity.min.js"></script>

Now that we have all the dependencies we need to configure the SDK with the "User Pool" id and the application client id. To keep this information private I created a config.js file that saves this information on two global variables.

Wrapping it all together

To see the SDK work at its best we are going to add a couple of scripts and we are also going to modify the index.html file to serve the login form as a template.

First, we are going to add an EventEmitter object that will handle events among the different elements of our app. Then we'll add an utils.js script that will inject some helper function to the global namespace. The first is a template function created by John Resig, which you can find on this link. The other is just a function that calls a callback function when the DOM is ready. Here is all the code involved.

Using this we can modify how the login form is template. First, we'll move the html code we have into a new script tag of type text/html . The reason behind this is that the browser doesn't recognize this script type, and so it leaves it there, without rendering. We can then reference it from another script and, by passing it through our tmpl function, render it on the screen.

In charge of the rendering order is the EventEmitter object. By modifying the LoginForm script and adding a new index.js file we'll render the form on the screen.

And we have our form on the screen.

Login form.

Sign up

Unfortunately, we need a user before we can try the login workflow. We could create the user right from Cognito's console, but I thought it would be good to do it from the app itself. Let's create a new SignupForm template with its corresponding script and let's add a couple of links to toggle each of them. Here is how the SignupForm template an string looks like:

It is basically the LoginForm but with an extra input to check if the password is correct. After writing it down, I'm inclined to try and make it work with just one template, but since this is just an example I am going to leave it as it is.

Now we have everything in place to set the sign-up flow against Cognito. We'll set it up so that when the form is submitted:

  1. Check that the password and confirm password match.
  2. Send the request to AWS Cognito using the pre-initialized SDK.
  3. Check if the request was successful and show an error or success message.

Here is the code for both functions.

This function will check that all the mandatory attributes are set on the user, return the result to the user, and trigger an email sent to the user to have him confirm their account.

Log in

Now we need to get the user in using its new credentials. The process is pretty much the same. The only difference is that we use the authenticateUser function from Cognito. There is still one thing that we need to solve though, before the user can connect. We need to set how the user is going to verify his account. There are many ways to do this. In this example I am going to let the user try to log in, and if he needs to verify his email, I am going to ask him to input the code that was sent to his inbox.

We'll just check if the returned error says that the user must confirm his email, and in that case, we'll redirect them to a new form where he can do just that. If he enters the correct code, we'll redirect them to the login form again, where he can input his credentials to get to the app.

Here is the code for the login function:

And this is the code to confirm the user's email, and the ability to resend the authorization code.

Log out

The last flow we should implement is the one to let the user log out. But first, we should welcome our user to our application, and make it impossible to get to the login, signup, or confirmation forms if he is authenticated. It would also be nice to get the user's parameters to personalize his experience inside our app.

We'll start by creating some function to:

  1. Check if a current user session exists.
  2. Get this user information.
  3. Handle authorization to each component.

When a user signs in to your application, Cognito stores its authentication token inside the browser localstorage. It uses JWT tokens to store and validate the users sessions. You can find more information about how JWT tokens work here.

Here are the functions we are going to use:

And this is how we implement them inside our Welcome page.

The only thing left to do is handle the sign out flow. Of course this is also handled by Cognito. We'll just wrap its signOut method into a Promise and then we'll plug it into our event handler handleSignOut .

Conclusion

The whole idea behind this article was to showcase how simple it is to set up our own authentication flows for our applications using AWS Cognito. There are still many options that I didn't showcase on this article that are really great, like: forget password flows, MFA authentication, remembering logins between devices, etc. I encourage you to try this library since I believe it will save an enormous amount of time, if you need custom authentication in your apps.

You can find all the code for this article on the following GitHub repository:

Thank you very much for your time and I hope it was helpful.

Feel free to correct any mistake or leave any feedback you have.

--

--