Securing APIs in Serverless (AWS Lambda)

Vinod Kisanagaram
Mar 9, 2019 · 7 min read

Serverless is here to stay and there’s exponential growth in APIs written on Serverless. Securing APIs is a tricky topic and below article is an attempt at stitching together all needed concepts for securing API to API calls that handle both authentication and authorization.

Image for post
Image for post
Secure Cloud

From bird’s eye view — we are going to do below things

  • Develop two simple APIs on AWS Lamda using NodeJS
  • Configure Cognito pools
  • Call API 1 from API 2 using Cognito

Lets develop a simple API that gives back day of birth as well as age by taking date of birth as request parameter.

In order to demonstrate API to API calls — let’s assume API 1 will give day of birth and API 2 will give the age. Also assume that client will always invoke API 1.

In short Client → API 1 (Day of birth) → API 2 (age).

Response will come from API 1 to client and API 2 is opaque to client.

Let’s develop API 2 (Age Calculator)

API response will be

{
"age": 31
}

Complete serverless code is available on Age Calculator repo — Github

Let’s develop API 1 (Day of Birth calculator)

API response will be

{
"day": "Friday"
}

Complete serverless code is available on Day of Birth repo — Github

So far..

Now we will call API 2 from API 1 so that end user would see both day of birth and age from single API

For this, lets change API 1 a bit so that we make fetch call to Age Calculator

Complete code is at Day of Birth Repo — Github

Back to security

We will create User Pool where we can on board accounts and Identity Pools to get access

Creating User Pool

Image for post
Image for post

Name the user pool to some meaningful name — for our example, we name it as ‘security-example-pool’

Image for post
Image for post

Select email sign in as option (you can change as you want — this is for demo purpose)

Image for post
Image for post

and then choose options to similar to below (you can change as you want — this is for quick demo purpose)

Image for post
Image for post
Image for post
Image for post

Leave all defaults in next 2 pages ‘Do you want to customize your email verification messages’ and ‘Do you want to add tags for this user pool’ and ‘Do you want to remember your user’s devices’ ‘Which app clients will have access to this user pool’ ‘Do you want to customize workflows with triggers’

Finally you will end up with below screen

Image for post
Image for post
Image for post
Image for post

Select ‘Create Pool’ which will create user pool. Once created, select the newly created user pool and note down the Pool Id and Pool ARN which we will use later

Now we’ll add app client that has access to this pool. On pool details page, select ‘App Client’ and details similar to below

Image for post
Image for post

which will create details like below

Image for post
Image for post

Note down the App Client ID

Creating Identity Pool

Image for post
Image for post

User Pool ID and Client ID are from earlier User Pool exercise

On selecting ‘Create Pool’ you will be shown screen with roles like below

Image for post
Image for post

Now we have User Pool and Identity Pool ready

Creating user and adding to User Pool

aws cognito-idp sign-up \
--region YOUR_COGNITO_REGION \
--client-id YOUR_COGNITO_APP_CLIENT_ID \
--username admin@example.com \
--password Passw0rd!

Eg:

aws cognito-idp sign-up --region us-east-1 --client-id 2su1g5iq748birpq5cscro8a45 --username test1@example.com --password Passw0rd!

Confirming signed up user

aws cognito-idp admin-confirm-sign-up \
--region YOUR_COGNITO_REGION \
--user-pool-id YOUR_COGNITO_USER_POOL_ID \
--username admin@example.com

Eg:

aws cognito-idp admin-confirm-sign-up --region us-east-1 --user-pool-id us-east-1_NfDyH1AB7 --username test1@example.com

If you get stuck on creating user pool and user — you can refer to clearer documentation at Serverless-Stack

Let’s add security to services

Resource ARN for this API will be similar to below

arn:aws:execute-api:us-east-1:900000005:p6q2ih1ryg/*/GET/age

Now using this ARN, let’s edit Identity Pool policy. Navigate to IAM console and select roles and search by prefix ‘Cognito_age_calc’ — this would show up 2 roles: poolAuth_Role and poolUnauth_Role

We are interested in Authenticated user’s role, so edit role’s policy with below (change IDs as needed)

{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"mobileanalytics:PutEvents",
"cognito-sync:*",
"cognito-identity:*"
],
"Resource": [
"*"
]
},
{
"Sid": "VisualEditor1",
"Effect": "Allow",
"Action": "execute-api:Invoke",
"Resource": "arn:aws:execute-api:us-east-1:900000005:p6q2ih1ryg/*/GET/age"
}
]
}

Back to code

authorizer: aws_iam

Entire function definition in serverless.yml is below

With that in place, once you deploy the function using ‘sls deploy’ and hit the request, you would notice this

{
"message": "Missing Authentication Token"
}

That’s awesome! We are now secure

Now time to hit API 2 from API 1 securely

In API 1 (day of birth service) terminal, do below installs

npm install amazon-cognito-identity-js aws-sdk crypto-js

and to API 1, add below 2 javascript files as utilities

SigV4Client.js

Security.js

Do note that Security.js expects below keys set in environment

COGNITO_REGION: us-east-1
COGNITO_USER_POOL_ID: us-east-1_SomeXYZ
COGNITO_IDENTITY_POOL_ID: us-east-1:553ad21e-f655-48ea-8444-SomePQR
COGNITO_APP_CLIENT_ID: 2su1g5iq748birpq5cscrsome
API_GATEWAY_REGION: us-east-1

In Serverless framework, recommended practice to set environments is in env.yml file. My env.yml looks like below

Day of Birth’s serverless.yml would look like below

Now let’s make change to original fetch call which was calling age calculator API with no security — below 2 methods are shown where calls are with security and no security

Notice that we are passing username and password first and getting token which later will be used to call API securely.

Bonus — I added my favorite ‘to’ method above that makes waiting on asynchronous calls neat to read

With all things in place, now it’s time to run the day of birth API — if everything is successful, you will see response similar to below

for GET — http://localhost:3000/day?dob=19880101

{
"day": "Friday",
"age": 31
}

That’s it.

Additional tips — instead of hard coding passwords, you can depend on AWS System Manager Parameter Store or AWS Secrets Manager (check costs)

Complete source code is available at

Credits — Serverless Stack

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch

Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore

Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store