Following is the video version of this tutorial
Security is one of the top priorities in modern web applications. This post is a step by step guide on how to build three layers of security for a single tenant application. In the next post I will describe on how to extend this architecture for a multi-tenant SAAS application.
We are going to build a Todo application with an angular frontend. It will use AWS Amplify library to interact with the backend services. Backend resources are easily setup with AWS Mobile Hub.
Following is a list of backend resources/services that will be used in the application.
1. Cognito User Pool
2. Cognito Identity Pool
6. API Gateway
User Authentication is the First Layer of Security. We are going to use the Cognito User Pool to authenticate our users into the application. While User Pool holds user information, we will attach Cognito Identity Pool to assign an IAM role for the authenticated user. Once user signed into the application he will receive JWT tokens (id_token, access_token, refresh_token) from Cognito User Pool. AWS Amplify library will store these tokens in the local storage of the browser. In fact the library will trade these tokens with UserPool and receive temporary AWS Credentials.(ACCESS_KEY_ID and SECRECT_ACCESS_KEY). Amplify library will use these temporary credentials to access backend resources by user.
Let's say the user wants to create a Todo item in the database. Amplify library will send the POST api call. Amplify signs this POST request with the temporary credentials before sending out to the API Gateway. Once API Gateway receives the request, it will extract the IAM role from the IAM credentials that the request is signed. Then it can look into the IAM policy document attached to the IAM role. If the IAM Policy allows to call the requested endpoint, API gateway will allow the request otherwise it will deny it. This is the Second Layer of Security.
If the request is allowed by API Gateway, it will then reach the Lambda function attached to the API Gateway path. Lambda function assumes an IAM role to connect with the database. The policy of the IAM role is constructed in a way that only the logged in user can access his records but not others. This is the Third Layer of Security.
Let's clone following Github repo. It contains angular application.
open up a terminal or a command prompt window and type,
git clone https://github.com/mjzone/mobilehub-tutorial myapp
cd into the myapp folder
Run npm install to install project dependencies
Open the project in VS Code or open it on any of your preferred IDE. I'm going to use VS Code.
Now log into your AWS console and go to the MobileHub service and create a new project. Select web and select Cloud enable your existing app.
Give your backend project a name. I called it TodoApp. Because that's what we are going to build. Click Next.
If you haven't setup credentials, create an IAM user with admin permissions and setup using the command awsmobile configure. Check my previous tutorial on how to do that.
Then we need to globally install awsmobile library.
npm install -g awsmobile-cli
Copy the init code from the next window. Go back to VSCode and paste the command and hit enter to link your application with mobile hub project.
Fill the prompt as follows assuming you setup your project inside myapp folder.
Go back to the Mobile Hub console and enable user-signin feature. Select Email and Password and Create a new userpool as shown below.
After that go back to the VSCode and type awsmobile pull to pull down configuration for Cognito User Pool.
Nice! Now it's time to run our app locally.
After the build is complete open up a browser window and type https://localhost:4200 to view the application.
Click SignUp and register a user in the User Pool. You should receive an email with the confirmation code. After copy pasting the code you are good to Sign in.
It's time to add our secure backend. We need to persist these todo items in a DynamoDB table securely. So the first step, to create a database in MobileHub.
Click on Add Table and click on Wizard to view a nice helpful wizard. Click "Yes Get Started" to open the wizard view.
We don't want other users to view anyone else's todos or update them. A user can read or update only his todos. So make the selections as shown below. If you are building an application where users can read others data although they cannot update others data, that can be easily configured. If you want users to view and update everyone else's data (Public table) that can also be configured. But in our case, we wanted a private table.
For a private/protected table Mobile hub adds the userId of the logged in user as the partition key for the table. That way it makes sure only the logged in user can read/update a record. This is the third level of security of our architecture. What are those three levels of security?
1. Authentication/Sign-in via Cognito
2. API level security - Make sure a user can only invoke endpoints that he is permitted
3. Database table row level security - Which we described above
Okay. Click Save and Continue
In the next screen, give a name to the database. I will call it "mytodos".As for the fields in our table, let's add todoId, todo, status.
We wanted to add todoId as the sortKey, so we can query specific todo of the user. We will not create indexes now. Click save and continue. And Save and finish in the next window to create the table.
Now that we have created the database, let's create an API to interact with the table via AWS Lambda function. But we are not going to create the API from MobileHub console, instead from the command line using MobileHub CLI. There is a reason for that. But first, pull down configurations of the database to our local project by running
In the command line interface (terminal/ VS Code's integrated terminal) run awsmobile command. This will list down all available commands with awsmobile CLI.
At the bottom of the results, you will see the features section. We need to enable cloud-api to interact with our database. We can directly ask mobile hub to create a REST api for our database table. This is why we chose CLI instead of mobilehub console. As of the writing of this post, AWS doesn't have that option in the mobilehub console.
awsmobile cloud-api enable --prompt
When you are prompted to choose the database table, choose our table name. See the image below.
Awesome! Now we have a REST API which connects our database table.
You can view the code, in the awsmobilejs/backend/cloud-api/mytodos/app.js
Mobilehub has created an expressJS application. When we deploy/publish our project it uses the aws-serverless-express middleware to wrap it in a lambda function. You can see the code for the wrapper at awsmobilejs/backend/cloud-api/mytodos/lambda.js
I suggest you look both the code to understand it better. Also see the documentation of the aws-serverless-express middleware at this github repository.https://github.com/awslabs/aws-serverless-express
Let's push the configuration on to the mobile hub by running,
This will take about a minute or two as it needs to zip the code, create CloudFormation script and run it. So be patient ;)
Nice. What shall we do next?
Well, now that we have the backend setup, let's connect our frontend angular application using AWS amplify library.
We need to configure aws-amplify with our project. This we did earlier. Here is the link to that video. Watch following tutorial for step by step guide.
Or here is the documentation from aws-amplify guiding you how to angular configuration.
Alright, hope you managed to do it. Now let's go to the home component found at myapp/src/app/home/home.component.ts
Make addTodo an async function as shown above. Let's await for the asynchronous database call to return successful.
In the above call "mytodosCRUD" is the API name. "/mytodos" is the main path that was configured. These configuration information can be found in the aws-export.js file. It is located at myapp/src/aws-exports.js
Similarly we can add other actions like Update/Delete.
You can find the complete example in this github repo