In part one of this series, we learned how to host a website in an AWS S3 bucket. Now that we have our site up and running, the next thing we need to provide is a way to secure it.
We want to allow users to create an account on our site and be able to log in. Generally, to accomplish this, we would create a database and an API on a server to authenticate against. However, with the introduction of AWS Cognito User Pools, we can now handle this the “serverless” way.
In this post, I will show you how to set up Cognito User Pools as an authentication provider. In simpler terms, I’m going to teach you how to create a simple username and password login that will provide an authentication mechanism for your website.
To get the most out of this post you will need an AWS account, as we will be spending a great deal of time in the AWS dashboard. If you do not have an account, you can sign up for one at https://aws.amazon.com/free/.
Before we can start authenticating, we must first setup a few things in AWS Cognito. On your AWS dashboard, you will find Cognito under Mobile.
To get started, click on Cognito and then choose Manage your User Pools. This will bring you to your User Pools dashboard. Click Create a User Pool.
This brings us to our wizard to create a User Pool. Enter a name for your User Pool, mine is set to Serverless. Then we are going to click Step through settings and look at the different options available to us.
On this step, you will see a list of attributes. These attributes are built in and can be used without any extra configuration. However, we want to make a few attributes required. Next to the options Email and Name, check the box for required. While we’re not going to do this step now, you can also check Alias for Email, Phone Number and Preferred Username.
This will allow you to use any of these as an alias for logging in. Skip the section on adding custom attributes and click Next Step.
In the next step you can set password policies. For now, let’s keep the defaults. Click Next Step to continue.
On the Verifications step, we have several things we can configure. The first is Multi-Factor Authentication (MFA). For this blog post, we are going to leave this set to Off.
The second option we have is to setup Verification of emails or phone numbers. I am setting a verification requirement on emails. I am not requiring a phone number right now as I am not collecting the phone number for this blog example. However, both work well and are instantaneous. You can also customize the email message your user will receive. Click Next Step to move on.
On the next section, we are going to add an app that will be authenticating against this User Pool. Click Add an app to get started.
Click Create app to commit your changes then click Next Step to move on. The next step you will see is the Triggers. Triggers implement AWS Lambda as serverless, micro-services to handle different functionalities for you. For the purpose of this blog, we are not going to create any triggers. However, in an upcoming post we will do a deep dive into the micro-services world. Click Next Step to continue on.
In the review section, you can get a snapshot of what your User Pool is going to look like. You can also modify any settings before you create it. Once you are satisfied, click Create pool to finish up.
That’s it! You now have a Cognito User Pool that replaces the need for a database and a server with an authentication API on it. We can now authenticate Serverlessly!
Show Me Some Code!
Ok, you’ve done the configuration and setup, now it’s time to write some code. In the code examples, we will continue to use the single page application (SPA) we created in part one of this series. Let me give you some reasons why I chose to build a SPA rather than a multi-page application (MPA).
- SPAs are more of an application feel and are, I believe, the present and future of websites.
- SPAs provide state. In a SPA we are not refreshing the entire page each time and are able to provide state. Therefore, in this example, once a user is authenticated they continue to be authenticated without having to ping the server on every load.
The SPA framework that I use is one called Aurelia and can be found at http://aurelia.io. I use the ES2016 webpack framework. At the end of this post, I will provide the entire package. For clarity and simplicity, the code is written in ES2016 but is straight forward, so it should be adaptable to something like angular or meteor. Also, included with the code I will provide a library of functions that can be used in a simple jQuery app as well. Ok let’s get coding!
One thing to note is, at the time of this writing, User Pools on AWS Cognito are in beta. They were released in April of 2016, and these prerequisites might and probably will change. First of all, we need to include several libraries. These libraries are dependencies of the Cognito SDK, Cognito Identity library or the AWS SDK. The libraries to include are:
- BN library for BigInteger computations (http://www-cs-students.stanford.edu/~tjw/jsbn/). You will need jsbn and jsbn2.
- AWS Cognito SDK
- AWS Cognito Identity
- AWS SDK
To make it easier you can use the current (as of this writing), versions listed here:
In addition to these global libraries, we also need to make a global setting that will happen each time your app loads. Let’s set the region for our AWSCognito library.
The Auth Class
We are going to start building an authentication class that can be used throughout your app. This library will consist of some global parameters and functions for registering, confirming, and logging in.
You will first notice that I am importing the Aurelia framework and a session class. I am using
inject from aurelia-framework as a helper to instantiate my
Session class. The session class is used to maintain state throughout the app. As we develop this app you will see this at work.
In addition to this, there are several global parameters we need to handle. We need to declare our userPoolId and our appClientId. If you are following along, this is where you will need to put your own info from when you created your user pool and app. With this data we will then construct our poolData and finally our userPool. This will provide class level data to the rest of the functions we are going to build.
There is a line of code that needs a bit of explaining.
This line is a bit of a hack this needed. For the AWS client SDK to make a call to a service it requires credentials to be populated. We can literally load this with anything and it will suffice until true credentials are provided.
So the first thing we need to do is build a way for our users to register. Registration involves the client posting credentials to the Cognito User Pool.
The rest of the code is pretty straight forward. The flow is as follows:
- The function accepts a user object that contains a name, email, username, and password.
- Email and Name are stored as Cognito User Attributes in an attributes array.
userPoolwe make a call to signup the user (if there is an error the code dies and outputs an error).
- On success, we set a
registeredflag on the session to true.
At this point, Cognito is going to store the user as a disabled user and send an email with a confirmation token in it. If you’ll remember, in the User Pool setup, we can choose email or phone validation. We can also choose to do both.
The next step in our process is to confirm the users email. Here is the code.
This should be fairly straight forward. The flow is as follow:
1. The function accepts a username and a code (confirmation code emailed to the user.
2. A local
cognitoUser is created using a new
cognitoUser we make a call to confirm the registration code (if there is an error the code dies and outputs an error).
4. On success, we set a
confirmed flag on the session to
If all checks out well, then Cognito changes the user status to enabled and we can now use our credentials to log in to the Cognito User Pool.
Finally, now that we have an enabled user, it’s time to authenticate our user and login.
So this function takes care of a few things. Let’s look at the flow on this one.
1. The function accepts a username and a password.
2. Several objects are built
authDetails using the
cognitoUser using the
3. Using the
cognitoUser, we then authenticate the user against the Cognito User Pool (if there is an error the code dies and outputs an error).
4. On success we set
session.user to be the
That’s it, your logged in!
Now that you can successfully login, we need a way to logout. Essentially, this is just clearing the cached user. The code is simple.
In this code we:
1. Instantiate a
2. Clear the user from our session
I am not sure it can get any simpler. At this point, the user is cleared from the AWS objects and removed from our session object.
At this point, we have a fully working authentication process. We can register, confirm, login, and logout from our application. Our state is stored in our session, so as we move around from page to page in our spa, our authenticated user is still present. However, what happens if we refresh the browser? Our session is not going to survive that. We have now lost our user. With AWS Cognito we have a solution for just that. Let’s take a look at the code.
In this function we are able to:
1. Get the current user from local storage and and instantiate a
2. If the user exists and not expired, then we can use
cognitoUser.getSession to get the current session.
a. If no user, or expired, then user is logged out to make sure all relevant data is gone.
Now, if our user refreshes the page, he will still be logged in. As far as the user is concerned, the session was never lost.
Now that we have a working authentication that keeps the user logged in, even if we refresh. Let’s add one more step to see it in action. When we registered our user we stored several properties. We stored a username, email, and name. However, currently, we are only getting the username back because it is part of the returned data when we authenticate. Let’s go ahead and grab the rest of our data and show it on a profile page.
This is actual a very simple process.
In this code we call the function,
getUserAttributes, that is hanging off our
session.user property. How does this work? Well if you’ll remember, when we successfully login we set
session.user = cognitoUser. This saves everything that we need to interact with our specific user in the Cognito User Pool. You’ll also notice that I have wrapped the return in a promise. Whenever I am returning data, I try to use a promise as it makes it much easier to interact with. Not necessary, just a preference of mine.
Put It All Together
So let’s take a look at the entire library.
Now that we have all our code in place. You can play around and see what you think. There are three ways to test the code.
You can play around with a working model located at http://serverless-architecture-simple.s3-website-us-east-1.amazonaws.com.
I have made my entire code base available for testing. The code base can be found at https://github.com/rackerlabs/serverless-demo/releases/tag/Blog-2. Once you download the code follow the instructions in the readme file to begin testing.
Local jQuery Implementation
Grab the same download as you would for the SPA. After unzipping, find the file called Standalone.html. Open this file in a browser and you can test the different functionalities we have created.
That’s it, you’re done. You now have a login process without having to stand up any servers or write any backend code. While it seems we are doing a lot here, we are really only scratching the surface. With triggers you can do so much more than a simple login. In our next post, we are going to explore the idea of micro-services and triggers using Lambda’s. In a full stack architecture, Lambda’s replace the backend API as a serverless alternative.