Serverless API to API authentication

Photo by Kenrick Mills on Unsplash

A practical guide to using Amazon Cognito to authenticate API to API integrations using an OAuth2 Client Credentials Grant flow, with code examples and visuals.

Introduction

In the Serverless World, there are always situations when developing solutions that two or more APIs need to communicate with each other synchronously.

The gold standard would be totally decoupled domain services using events, but in reality, there are often reasons why you need a machine to machine style communication using REST.

This blog post covers using the OAuth2 Client Credentials Grant flow to authenticate APIs (machines) rather than users, using AWS Cognito, API Gateway, AWS Lambda, and TypeScript, hosted in a Lerna monorepo for ease of the demo. You can view the code repo here.

We will be building the following serverless architecture below:

Example architecture for the solution we are building

The architecture works as follows:

  1. The user invokes the create orders POST API endpoint. (for ease of demo this endpoint does not have any authentication)
  2. The backend lambda then generates an access token for the deliveries API with the required scopes (in this scenario create.delivery).
  3. The access token which is returned from AWS Cognito is then sent in the Authorization header on the request to the deliveries API i.e. the create delivery endpoint.
  4. Our API Gateway configuration for the deliveries API will automatically verify the token for us i.e. is it valid, it hasn’t expired, issued by the correct provider, it has the correct scopes on the token for this specific endpoint etc
  5. If it is a valid token we allow API Gateway to invoke the correct lambda. If it is invalid then the request will be rejected.

? Note: Typically the lambda would be interacting with a backend data store like DynamoDB, but we are just going to log the console outputs too CloudWatch to see what is going on, as this demo is just to show the M2M flow using Cognito.

What are the alternatives?

Typically in my experience some teams will use API Keys alone for authentication and authorisation which are a bad idea and not best practice:

“Don’t rely on API keys as your only means of authentication and authorization for your APIs. For one thing, if you have multiple APIs in a usage plan, a user with a valid API key for one API in that usage plan can access all APIs in that usage plan. Instead, use an IAM role, a Lambda authorizer, or an Amazon Cognito user pool.” — https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-api-usage-plans.html

Some of the other alternatives for controlling access to API Gateway are detailed here, however the benefits of using the approach in this article is that it can also be used with other services such as AWS AppSync since it is an industry standard, and can also be coupled with Lambda Authorisers too.

Let’s get this deployed! ?

? Note: Running the following commands will incur charges on your AWS account so change the config accordingly.

? Note: The example code is not production ready and has verbose comments to highlight the overall concept and architecture.

The solution is split into three serverless apps within a Lerna monorepo:

  1. ? Auth Service (AWS Cognito clients, resource servers etc)
  2. ?️ Order Service API (Public facing client i.e. consumer)
  3. ? Delivery Service API (Internal resource server)

We can deploy them running the steps below.

Getting the Auth resources deployed ?

In the base of the repo run npm i and then npm run bootstrap (this will install the dependencies and then bootstrap Lerna)

Once the steps above are completed, run the following command to deploy the Authentication Service from the ./apps/auth-service folder: npm run deploy:develop

Once deployed, to test getting an access token back you can run the following CURL command using your terminal (replacing the placeholders)

curl -X POST --user <client_id_here>:<client_secret_here> 'https://serverless-auth-service-develop.auth.eu-west-1.amazoncognito.com/oauth2/token?grant_type=client_credentials&scope=serverless-auth-service-develop/cancel.delivery' -H 'Content-Type: application/x-www-form-urlencoded'

Or alternatively you can use Postman by creating a new resource, click on the Authorization tab, change the type to ‘OAuth 2.0’, and fill in the details before clicking on ‘Get New Access Token’:

Example of the Postman screen where you will need to add the client ID and client secret

If you take the resulting access_token which is returned, you can view the contents of the JWT using https://jwt.io/ (as shown in the screenshot below)

example of a decoded access token from AWS Cognito

As you can see from the access token which has been generated it has the following:

  1. It only has the cancel.delivery scope as this was all that was requested with the token (if you leave this blank you will get all available scopes back that the client has)
  2. It has an exp property which is the epoch date/time of expiry.
  3. It has a unique sub and client_id property which are the app client ID (i.e. the Orders API)
  4. The iat property which is the issued at epoch date/time.
  5. It has a jti property which is the unique identifier of this particular token.
  6. It has an iss property which is the identity provider who issued the token, which in our scenario is our AWS Cognito user pool.

Getting the Deliveries API deployed ?

Once the steps above are completed, run the following command from the ./apps/deliveries directory to deploy the Deliveries Service: npm run deploy:develop

? Note: In the Auth Service serverless.yml we push the arn of the UserPool to Parameter Store here:

# push the value to parameter store
UserPoolArnSSMParameter:
Type: AWS::SSM::Parameter
Properties:
Name: '/serverless-auth/${self:provider.stage}/userpool-arn'
Type: String
Value:
Fn::GetAtt:
- UserPoolResource
- Arn
Description: SSM Parameter for cognito userpool arn.

To consume in the deliveries serverless.yml stack here:

authorizer:
# import the arn via ssm
arn: ${ssm:/serverless-auth/${self:provider.stage}/userpool-arn}
scopes:
- serverless-auth-service-develop/create.delivery

The reason we need to do this using SSM rather than CloudFormation Outputs and Inputs across stacks is an issue currently with the Serverless Framework for this Authorizer property due to the underlying CloudFormation for API Gateway.

Getting the Orders API deployed ?️

Finally, once the steps above are completed, run the following command from the ./apps/orders directory to deploy the Orders Service: npm run deploy:develop

This is all of the relevant services now deployed! ?

Testing this end to end ✔️

Let’s now test this end to end to see this in action and look at the resulting logs in CloudWatch.

You can import the Postman file serverless-auth-service.postman_collection.json from the ./postman folder which will allow you to hit the Create Order endpoint, returning a similar response as shown below:

Example response from the Create Order endpoint

You can now use CloudWatch Log Insights to collate your logs across both domain services via theCorrelationID property from the response above using the following query (results shown below for our example correlation ID)

fields @timestamp, @message
| filter @message like /a9b0f153-8cd7-4257-8ff9-34ce33678738/
| sort @timestamp asc
| limit 50
Using CloudWatch Log Insights to view all calls by CorrelationId across multiple log groups

You can see from the collated logs above that the Orders service generated an access token with the correct scope to allow it to successfully call the internal Deliveries service.

Caching tokens

When it comes to a production service you will probably want to cache the access tokens rather than generating them over and over again in each lambda invocation (especially if you set them to expire after an hour which is the max).

The diagram below shows how lambdas scaling out horizontally without caching would keep generating the same access tokens over and over again to be used only once each time.

Diagram showing lambdas scaling out, each generating a new token

How would we fix this with caching?

In reality you would likely cache the tokens in a service such as Parameter Store, and use the App Config Lambda Layer to pull the token into various Lambdas automatically and cache it; and if the token is expiring soon, then that Lambda can regenerate the access token once and push it back to Parameter Store for the subsequent scaled out lambdas to pick up. (I will show this in a follow up article)

The architecture for this is shown below:

Diagram showing the use of App Config and Lambda Layers to store access tokens

I have covered using the App Config Lambda Layer in the following blog post:

Serverless Feature Flags ?

Wrapping up

This is a super basic example to show the premise of how to authenticate API to API communication, but I hope you found it useful and a base to work from! In a follow up article (V2) I will take this example further and show:

  1. Caching of the access tokens with different approaches.
  2. Using this approach with other AWS Services as a single way of authenticating API’s in your solutions (such as with AWS AppSync, using Lambda Authorisers etc)

I would love to connect with you on any of the following:

https://www.linkedin.com/in/lee-james-gilmore/
https://twitter.com/LeeJamesGilmore

If you found the articles inspiring or useful please feel free to support me with a virtual coffee https://www.buymeacoffee.com/leegilmore and either way lets connect and chat! ☕️

If you enjoyed the posts please follow my profile Lee James Gilmore for further posts/series, and don’t forget to connect and say Hi ?

Please also use the ‘clap’ feature at the bottom of the post if you enjoyed it! (You can clap more than once!!)

About me

Hi, I’m Lee, an AWS certified technical architect and Lead Software Engineer based in the UK, currently working as a Technical Cloud Architect and Principal Serverless Developer, having worked primarily in full-stack JavaScript on AWS for the past 5 years.

I consider myself a serverless evangelist with a love of all things AWS, innovation, software architecture and technology.

** The information provided are my own personal views and I accept no responsibility on the use of the information. ***


Serverless API to API authentication? was originally published in Level Up Coding on Medium, where people are continuing the conversation by highlighting and responding to this story.


This content originally appeared on Level Up Coding - Medium and was authored by Lee James Gilmore

Photo by Kenrick Mills on Unsplash

A practical guide to using Amazon Cognito to authenticate API to API integrations using an OAuth2 Client Credentials Grant flow, with code examples and visuals.

Introduction

In the Serverless World, there are always situations when developing solutions that two or more APIs need to communicate with each other synchronously.

The gold standard would be totally decoupled domain services using events, but in reality, there are often reasons why you need a machine to machine style communication using REST.

This blog post covers using the OAuth2 Client Credentials Grant flow to authenticate APIs (machines) rather than users, using AWS Cognito, API Gateway, AWS Lambda, and TypeScript, hosted in a Lerna monorepo for ease of the demo. You can view the code repo here.

We will be building the following serverless architecture below:

Example architecture for the solution we are building

The architecture works as follows:

  1. The user invokes the create orders POST API endpoint. (for ease of demo this endpoint does not have any authentication)
  2. The backend lambda then generates an access token for the deliveries API with the required scopes (in this scenario create.delivery).
  3. The access token which is returned from AWS Cognito is then sent in the Authorization header on the request to the deliveries API i.e. the create delivery endpoint.
  4. Our API Gateway configuration for the deliveries API will automatically verify the token for us i.e. is it valid, it hasn’t expired, issued by the correct provider, it has the correct scopes on the token for this specific endpoint etc
  5. If it is a valid token we allow API Gateway to invoke the correct lambda. If it is invalid then the request will be rejected.
? Note: Typically the lambda would be interacting with a backend data store like DynamoDB, but we are just going to log the console outputs too CloudWatch to see what is going on, as this demo is just to show the M2M flow using Cognito.

What are the alternatives?

Typically in my experience some teams will use API Keys alone for authentication and authorisation which are a bad idea and not best practice:

“Don’t rely on API keys as your only means of authentication and authorization for your APIs. For one thing, if you have multiple APIs in a usage plan, a user with a valid API key for one API in that usage plan can access all APIs in that usage plan. Instead, use an IAM role, a Lambda authorizer, or an Amazon Cognito user pool.” — https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-api-usage-plans.html

Some of the other alternatives for controlling access to API Gateway are detailed here, however the benefits of using the approach in this article is that it can also be used with other services such as AWS AppSync since it is an industry standard, and can also be coupled with Lambda Authorisers too.

Let’s get this deployed! ?

? Note: Running the following commands will incur charges on your AWS account so change the config accordingly.
? Note: The example code is not production ready and has verbose comments to highlight the overall concept and architecture.

The solution is split into three serverless apps within a Lerna monorepo:

  1. ? Auth Service (AWS Cognito clients, resource servers etc)
  2. ?️ Order Service API (Public facing client i.e. consumer)
  3. ? Delivery Service API (Internal resource server)

We can deploy them running the steps below.

Getting the Auth resources deployed ?

In the base of the repo run npm i and then npm run bootstrap (this will install the dependencies and then bootstrap Lerna)

Once the steps above are completed, run the following command to deploy the Authentication Service from the ./apps/auth-service folder: npm run deploy:develop

Once deployed, to test getting an access token back you can run the following CURL command using your terminal (replacing the placeholders)

curl -X POST --user <client_id_here>:<client_secret_here> 'https://serverless-auth-service-develop.auth.eu-west-1.amazoncognito.com/oauth2/token?grant_type=client_credentials&scope=serverless-auth-service-develop/cancel.delivery' -H 'Content-Type: application/x-www-form-urlencoded'

Or alternatively you can use Postman by creating a new resource, click on the Authorization tab, change the type to ‘OAuth 2.0’, and fill in the details before clicking on ‘Get New Access Token’:

Example of the Postman screen where you will need to add the client ID and client secret

If you take the resulting access_token which is returned, you can view the contents of the JWT using https://jwt.io/ (as shown in the screenshot below)

example of a decoded access token from AWS Cognito

As you can see from the access token which has been generated it has the following:

  1. It only has the cancel.delivery scope as this was all that was requested with the token (if you leave this blank you will get all available scopes back that the client has)
  2. It has an exp property which is the epoch date/time of expiry.
  3. It has a unique sub and client_id property which are the app client ID (i.e. the Orders API)
  4. The iat property which is the issued at epoch date/time.
  5. It has a jti property which is the unique identifier of this particular token.
  6. It has an iss property which is the identity provider who issued the token, which in our scenario is our AWS Cognito user pool.

Getting the Deliveries API deployed ?

Once the steps above are completed, run the following command from the ./apps/deliveries directory to deploy the Deliveries Service: npm run deploy:develop

? Note: In the Auth Service serverless.yml we push the arn of the UserPool to Parameter Store here:
# push the value to parameter store
UserPoolArnSSMParameter:
Type: AWS::SSM::Parameter
Properties:
Name: '/serverless-auth/${self:provider.stage}/userpool-arn'
Type: String
Value:
Fn::GetAtt:
- UserPoolResource
- Arn
Description: SSM Parameter for cognito userpool arn.
To consume in the deliveries serverless.yml stack here:
authorizer:
# import the arn via ssm
arn: ${ssm:/serverless-auth/${self:provider.stage}/userpool-arn}
scopes:
- serverless-auth-service-develop/create.delivery
The reason we need to do this using SSM rather than CloudFormation Outputs and Inputs across stacks is an issue currently with the Serverless Framework for this Authorizer property due to the underlying CloudFormation for API Gateway.

Getting the Orders API deployed ?️

Finally, once the steps above are completed, run the following command from the ./apps/orders directory to deploy the Orders Service: npm run deploy:develop

This is all of the relevant services now deployed! ?

Testing this end to end ✔️

Let’s now test this end to end to see this in action and look at the resulting logs in CloudWatch.

You can import the Postman file serverless-auth-service.postman_collection.json from the ./postman folder which will allow you to hit the Create Order endpoint, returning a similar response as shown below:

Example response from the Create Order endpoint

You can now use CloudWatch Log Insights to collate your logs across both domain services via theCorrelationID property from the response above using the following query (results shown below for our example correlation ID)

fields @timestamp, @message
| filter @message like /a9b0f153-8cd7-4257-8ff9-34ce33678738/
| sort @timestamp asc
| limit 50
Using CloudWatch Log Insights to view all calls by CorrelationId across multiple log groups

You can see from the collated logs above that the Orders service generated an access token with the correct scope to allow it to successfully call the internal Deliveries service.

Caching tokens

When it comes to a production service you will probably want to cache the access tokens rather than generating them over and over again in each lambda invocation (especially if you set them to expire after an hour which is the max).

The diagram below shows how lambdas scaling out horizontally without caching would keep generating the same access tokens over and over again to be used only once each time.

Diagram showing lambdas scaling out, each generating a new token

How would we fix this with caching?

In reality you would likely cache the tokens in a service such as Parameter Store, and use the App Config Lambda Layer to pull the token into various Lambdas automatically and cache it; and if the token is expiring soon, then that Lambda can regenerate the access token once and push it back to Parameter Store for the subsequent scaled out lambdas to pick up. (I will show this in a follow up article)

The architecture for this is shown below:

Diagram showing the use of App Config and Lambda Layers to store access tokens

I have covered using the App Config Lambda Layer in the following blog post:

Serverless Feature Flags ?

Wrapping up

This is a super basic example to show the premise of how to authenticate API to API communication, but I hope you found it useful and a base to work from! In a follow up article (V2) I will take this example further and show:

  1. Caching of the access tokens with different approaches.
  2. Using this approach with other AWS Services as a single way of authenticating API’s in your solutions (such as with AWS AppSync, using Lambda Authorisers etc)

I would love to connect with you on any of the following:

https://www.linkedin.com/in/lee-james-gilmore/
https://twitter.com/LeeJamesGilmore

If you found the articles inspiring or useful please feel free to support me with a virtual coffee https://www.buymeacoffee.com/leegilmore and either way lets connect and chat! ☕️

If you enjoyed the posts please follow my profile Lee James Gilmore for further posts/series, and don’t forget to connect and say Hi ?

Please also use the ‘clap’ feature at the bottom of the post if you enjoyed it! (You can clap more than once!!)

About me

Hi, I’m Lee, an AWS certified technical architect and Lead Software Engineer based in the UK, currently working as a Technical Cloud Architect and Principal Serverless Developer, having worked primarily in full-stack JavaScript on AWS for the past 5 years.

I consider myself a serverless evangelist with a love of all things AWS, innovation, software architecture and technology.

** The information provided are my own personal views and I accept no responsibility on the use of the information. ***


Serverless API to API authentication? was originally published in Level Up Coding on Medium, where people are continuing the conversation by highlighting and responding to this story.


This content originally appeared on Level Up Coding - Medium and was authored by Lee James Gilmore


Print Share Comment Cite Upload Translate Updates
APA

Lee James Gilmore | Sciencx (2021-08-11T00:53:49+00:00) Serverless API to API authentication. Retrieved from https://www.scien.cx/2021/08/11/serverless-api-to-api-authentication/

MLA
" » Serverless API to API authentication." Lee James Gilmore | Sciencx - Wednesday August 11, 2021, https://www.scien.cx/2021/08/11/serverless-api-to-api-authentication/
HARVARD
Lee James Gilmore | Sciencx Wednesday August 11, 2021 » Serverless API to API authentication., viewed ,<https://www.scien.cx/2021/08/11/serverless-api-to-api-authentication/>
VANCOUVER
Lee James Gilmore | Sciencx - » Serverless API to API authentication. [Internet]. [Accessed ]. Available from: https://www.scien.cx/2021/08/11/serverless-api-to-api-authentication/
CHICAGO
" » Serverless API to API authentication." Lee James Gilmore | Sciencx - Accessed . https://www.scien.cx/2021/08/11/serverless-api-to-api-authentication/
IEEE
" » Serverless API to API authentication." Lee James Gilmore | Sciencx [Online]. Available: https://www.scien.cx/2021/08/11/serverless-api-to-api-authentication/. [Accessed: ]
rf:citation
» Serverless API to API authentication | Lee James Gilmore | Sciencx | https://www.scien.cx/2021/08/11/serverless-api-to-api-authentication/ |

Please log in to upload a file.




There are no updates yet.
Click the Upload button above to add an update.

You must be logged in to translate posts. Please log in or register.