AWS Cloud Operations & Migrations Blog

Configuring machine to machine Authentication with Amazon Cognito and Amazon API Gateway – Part 2

This blog is the second part to a 2 part series on how to secure your Amazon API Gateway with Amazon Cognito, in machine to machine (M2M) communication use cases.

In the previous blog post, we dove deep into the different use cases involving M2M communication and how it contributes to business modernization, and why securing this type of communication is important.

In this post, we will walk you through a step-by-step solution for implementing secure, authentication based, M2M communication in your own environment. Here’s a high-level overview, and an architectural diagram of the solution we will be building.

The image shows a flow diagram where a client authenticates through Amazon Cognito to obtain an access token, which is then used to make a request to an API Gateway.

Fig-1: Architecture

Step A: Client Authentication – The client represents the machine or service that needs to access the protected API. It has credentials, such as a client ID and potentially a client secret, that it uses to authenticate by sending a request to Amazon Cognito.

Step B: Access Token – Amazon Cognito validates the client’s ID and secret to ensure the client is registered and authorized to obtain an access token. After successful authentication, Amazon Cognito issues an access token to the client. This token is a representation of the client’s credentials and permissions to access the API.

Step C: Client Request with Access Token – The client now makes a request to the Amazon API Gateway, including the access token in the request’s authorization header. Amazon API Gateway validates the access token with Amazon Cognito to ensure it is valid and has not expired and grants or denies access based on token validity.

This flow ensures that the client’s credentials are securely passed to the authorization server (Cognito) to obtain a temporary access token. The client then uses this token to access the protected API, without needing to expose its sensitive credentials directly to the API Gateway.

This blog is intended to show how it works at a high-level, so we will use Postman to demonstrate this. In a real-world scenario when it is implemented in a service, postman will not be used. For this demonstration you will send the request with the Client ID and secret to the Authorization endpoint. This will provide a response with the token, and you send the second request with that token to the Amazon API Gateway to get access to the API. In a production application, all of that logic will be managed by the requester and Postman will be out of the picture.

In the next steps we’ll walk you through the steps required to build the solution on AWS using the console.

Implementation

Prerequisites

  1. AWS Account Setup.
  2. IAM User with permissions to perform the duties below but applying least-privilege permissions. Please refer to this for policy examples for Amazon Cognito and this for Amazon API Gateway.
  3. Postman: To demonstrate the high-level functionality of the API authentication flow using Amazon Cognito and Amazon API Gateway.

Step 1: Authorization Server Endpoint set up:

In this step, you will create an Amazon Cognito use pool, create a confidential client and OAuth 2.0 Client credentials grant type which will be used for M2M authentication. Next, you will create a custom resource server with 2 custom scopes – one for read access and one for write access which will be associated with the confidential client to request access to resources when making API requests. This setup ensures that the external client can securely authenticate and obtain access tokens to interact with the protected API, without needing to expose sensitive credentials directly.

1. Sign in to the AWS Management Console and open the Amazon Cognito console

2. In the navigation pane, under Amazon Cognito, choose User Pools.

3. Choose Create user pool (here you have 2 options, or the orange button, or the button in the middle of the screen). We’ll walk you through the wizard’s steps:

In Step 1 select User name

In Step 2, under Multi-factor authentication, select No MFA

In Step 3, leave everything as default

In Step 4, under Email provider, select Send email with Cognito

In Step 5, we setup the app integration:

Enter a name for the user pool, and under Hosted authentication pages, select Use the Cognito Hosted UI for sign-up and sign-in flows. Also, you will need to enter a Cognito domain, that will serve as the authorization endpoint that the client needs.

The image depicts the "Integrate your app" step in the Amazon Cognito user pool setup, where you configure user pool name, hosted authentication pages, and domain settings.

Fig-2: Integrate your app

You want to create a confidential client for M2M authentication even if the client will be used with APIs or machine clients that reside outside of AWS. Confidential here means it is a client with a secret and to be used by machines as mentioned in the first part of the blog series.

4. Choose Generate a client secret to generate a client credentials grant.

The image displays the "Initial app client" configuration step in Amazon Cognito, where you set up app type, client name, secret, allowed callback URLs, and advanced settings.

Fig-3: App client configuration

Now you can configure the advanced app client settings. You need to add the client credentials grant, under the OAuth 2.0 grant types. However, as the message indicates “Client credentials grant type is disabled during the creation of the event”. So, go ahead and click through to Next and create the user pool. You can add the client credentials grant later.

In the Step 6, we review the configuration, then select Create user pool.

5. Now we need to create the resource server. A resource server is a representation of the backend server that you will be protecting using access tokens. The client request will be authorized by the Amazon Cognito resource server. We will need to create a scope – a level of access that an app can request to a resource. In an Amazon Cognito access token, the scope is backed up by the trust that you set up with your user pool: a trusted issuer of access tokens with a known digital signature. We’ll walk you through the steps:

A. Select the User pool created in the previous step, and choose the App Integration tab

B. Select Create resource server, enter a Resource server name and identifier.

C. Create two new custom scopes, for read and write as shown below, then select Create resource server.

The image shows the "Create resource server" step in Amazon Cognito, where you configure the resource server name, identifier, and custom scopes for OAuth authorization.

Fig-4: Resource server configuration

6. In the App Integration section, scroll down to App client list, locate the App client created in a previous step and select the link to open it.

In the Hosted UI section, choose Edit. Here we add the Client credentials grant under the OAuth 2.0 grant types. We also need to add in the Custom scopes. The scope here defines what actions and resources the application is allowed to access. In our case, we have a custom scope which allows the application read and write access. Choose Save changes.

The image displays the "Edit Hosted UI" step in Amazon Cognito, where you configure allowed callback URLs, sign-out URLs, identity providers, OAuth 2.0 grant types, and custom scopes for the hosted sign-up and sign-in pages.

Fig-5: Hosted UI configuration

Step 2: Amazon Cognito Integration with Amazon API Gateway

In this step, we will configure the integration between Amazon API Gateway and Amazon Cognito to secure the API exposure.

1. In the navigation pane, choose Amazon API Gateway.

2. Choose Create API and then choose Build on the Rest API section.

3. Enter a meaningful API name, select regional as the API endpoint type and choose Create API.

The image shows the "Create REST API" screen in AWS API Gateway, where you can create a new API, clone an existing API, import an API, or use an example API, and specify the API name, description, and endpoint type.

Fig-6: Creating a REST API

4. Now, choose Create Resource, and add in a resource name such as /blog. Choose Create Resource.

The image shows the "Create resource" screen in AWS API Gateway, where you specify the resource path and name, with options to enable proxy resource and CORS (Cross-Origin Resource Sharing).

Fig-7: Creating a resource

5. Select Create method from the right-hand side, select GET from the Method type. Select Mock as the Integration type and choose Create method.

We will now create the response that will help to validate the security of our integration.

To Mock the answer you will need to create a Mapping Template in the integration response. The template will return that Amazon API Gateway was successful to read, and 200 status code.

6. Select the Integration response tab and choose Edit to change the mapping template.

The image displays the method execution details for a GET request on the /blog resource in AWS API Gateway, showing the flow from client request to integration response, with options to edit or delete the integration response.

Fig-8: Method execution

7. Expand the Mapping template section to add the following text into the Template body and choose Save.

{

"status": "successful read"

}

The image shows the "Edit integration response" screen in AWS API Gateway, where you can configure the response details, header mappings, and mapping templates, including specifying HTTP status regex, method response status code, content handling, and the JSON response body.

Fig-9: Integration response

7. Choose Create method, to create another method under /blog. This time, select POST from the Method type. Select Mock as the Integration type and choose Create method.

Expand the Mapping template section to add the following text into the Template body and choose Save:

{
"status" : "successful write"
}

8. Choose Deploy API in a New stage called Prod.

The image displays the method execution details for a POST request on the / resource in AWS API Gateway, showing the client request flow through method request, integration request, integration response, and method response.

Fig-10 Deploy API

After the deployment you can check the URL to be invoked from the Invoke URL section of the Stage details:

The image shows the "Stage details" for the 'Prod' stage in AWS API Gateway, indicating that both the cache cluster and default method-level caching are inactive, along with the invoke URL for accessing the API.

Fig-11: Invoke URL

Step 3: Testing the API Gateway using Postman

In this step, you will be using Postman API Platform to make requests and test the API Gateway. To do this, enter the invoke URL followed by /blog and this will return a successful read message for a GET method, and a write message for a POST method. In this configuration, the API is not secure because we have no authorization configured.

The image shows a GET request being sent to the endpoint of an API in AWS API Gateway using Postman, with the response body displaying a JSON object indicating a successful read: {"status": "successful read"}.
Fig-12: Postman

To add a security layer, you will add the Amazon Cognito integration piece that we configured in a previous step:

1. In the navigation pane, under Amazon API Gateway, choose Authorizers.

2. Select Create Authorizer and enter a suitable Authorizer name. Change the Authorizer type to Cognito, and select your user pool from the Cognito user pool section. Enter the Token source as Authorization and select Create authorizer.

The image shows the "Create authorizer" screen in AWS API Gateway, where you configure the authorizer details such as name, type (Lambda or Cognito), Cognito user pool, token source, and optional token validation for the selected API.

Fig-13: Create Authorizer

3. Add this authorizer to the 2 methods you created – GET and POST. To do this, select Edit method request, for the GET method and enter blog/read under the Authorization Scopes. Make sure to update the POST method and enter blog/write under the Authorization Scopes.

4. Redeploy the API to the Prod stage to test the new secure API.

When you retest this in Postman, it should give an Unauthorised message and a 401 error. This is because an authorization token is not being sent.

The image shows a GET request being sent to the endpoint of an API in AWS API Gateway using Postman, with the response body displaying a JSON object indicating an unauthorized access error: {"message": "Unauthorized"}.

Fig-14: Unauthorised message

5. To address this, include the token in the call. Within Postman, add the Authorization type to OAuth2.0, change the Grant type to Client Credentials and add the Cognito Domain URL. This can be found in the App Integration section of the User pool in the Amazon Cognito console. Make sure to add the /oauth2/token at the end.

The Client ID and Client Secret can be found in the App Integration section of your User pool. Click on the App client list and select your App Client to view it.

The image shows the details of an app client named "MyApp" in Amazon Cognito, displaying the app client name, client ID, client secret (hidden), and enabled authentication flows, including ALLOW_REFRESH_TOKEN_AUTH and ALLOW_USER_SRP_AUTH.

Fig-15: App Client

The image shows the "Configure New Token" screen in Postman, where you set up an OAuth 2.0 token with the grant type set to Client Credentials, and fields for Access Token URL, Client ID, Client Secret, and Scope are filled in.

Fig-16: Configuring a new token with the details

6. Scroll down the page and select Get New Access Token.

After successfully authenticating, Postman will show the following message: “authentication complete”.

This functionality of Postman will capture the token that will be used in the Token section to have authorization to call our API.

The image shows a POST request being sent to the endpoint of an API in AWS API Gateway using Postman, with the Authorization tab displaying a bearer token, and the response body displaying a JSON object indicating a successful write: {"status": "successful write"}.

Fig-17: Successful write

7. Repeat the step mentioned above for the GET method. It will also provide a successful read.

Cleaning up

Deploying this solution in your AWS account will incur costs. To avoid ongoing charges, when you’re done examining the solution, delete the resources that were created, such as the API Gateway resource, and Amazon Cognito user pool.

This is a Mock, how would this work in real life?

This blog is intended to show how it works at a high-level, and hence Postman is used to demonstrate this. In a real-world scenario when it is implemented in a service, postman will not be used. For this demonstration you will send the request with the Client ID and secret to the Authorization endpoint. This will provide a response with the token, and you send the second request with that token to the Amazon API Gateway to get access to the API. In a production application, all of that logic will be managed by the requester and Postman will be out of the picture.

How our solution differs from using AWS App Mesh?

AWS offers a great breadth of services, catering to different use cases, such as AWS App Mesh. App Mesh helps you streamline operations, implement custom traffic routing rules, and configure and standardize the traffic flows between services. While this sounds similar to the solution covered in this blog, there is one caveat – how the network works. AWS App Mesh excels in handling East-West traffic between services, such as internal traffic and auditing. Our solution is for North-South traffic, such as externally originated traffic. Therefore, the solution we’ve walked you through is more suitable than App Mesh for the use case.

Conclusion

If you’re looking to build a secure machine-to-machine (M2M) authentication solution on AWS, go ahead and follow the steps outlined in this post. We’ve walked you through the process of setting up an M2M authentication solution using Amazon Cognito and Amazon API Gateway, with the client credentials grant. Integrate Amazon Cognito with Amazon API Gateway to create a secure REST API. Test it out using Postman, where you can enter the invoke URL and see the successful read/write messages.

Now is the time to accelerate your business modernization journey. Reach out to your AWS team, and together we’ll show you how to leverage the full potential of your digital assets and creating a scalable, manageable, and secure infrastructure that can adapt to your evolving business needs.

About the authors:

Keerthana Ganesh

Keerthana Ganesh is a Security and Compliance Solutions Architect at AWS based in the UK, where she advises EMEA customers on building secure cloud architectures. In this role, she helps customers to build robust, resilient, and secure cloud environments that meet their evolving business and regulatory requirements.

Jorge Alvarez

Jorge Alvarez is a Solutions Architect Specialist in Migration and Modernization at AWS. In his role, he provides guidance to customers in their Migration and Modernization journey in EMEA. Prior to becoming a solutions architect at AWS, Jorge was leading teams in the cloud space, helping customers in their journey and modernization efforts.