Authorization in machine-to-machine integrations using Amazon Cognito
- Apr 18, 2024
- 5 min read
By Piotr Grzywa, Backend Engineer
Tech is one of the pillars of Kitopi’s success. One cannot imagine modern software systems without a cloud provider. So, it is for Kitopi’s case - we are using AWS, utilizing heavily services provided by Amazon. In this article I am going to show you how to set up an authorization for machine-to-machine integration in AWS, considering usage of Amazon Cognito as a service for authentication and authorization. In this article you will set up a mock resource behind API Gateway, then configure Cognito user pool supporting client credentials flow, and lastly connect it with the API. Examples provided have infrastructure’s code written in Terraform, so basic knowledge of the tool is needed to go through them. Article ends with a brief description of alternatives to authorization based on OAuth 2.0.
Authorizing clients of M2M integration with OAuth2.0
OAuth 2.0 protocol has a dedicated flow which is suitable for M2M scenarios where the client application is trusted and there is no user involvement in the authentication process. The flow (or grant as it is called in the protocol) is called Client Credentials. It provides a direct and efficient way for the client to obtain an access token from the authorization server and access protected resources. As with any OAuth 2.0 grant, the token expires within configured time, and (based on the implementation) offers an option to revoke it. However, it is important to ensure that the client credentials (client ID and client secret) are securely stored and transmitted.
API Gateway setup
To check the setup, we should prepare a resource (unprotected at the beginning) that we are going to share with the other side of the integration. We will achieve it by setting up a mock endpoint, which will return a successful message of welcome to the world every time it gets called. To achieve that, we need to set up multiple terraform resources as given below (use api_gateway.tf as filename). Once applied with terraform against your AWS infrastructure, you should see a new API called example-api and single resource /hello-world, returning a mocked response every time it gets called with the GET method.
provider "aws" {
region = "eu-west-1"
}
resource "aws_api_gateway_rest_api" "example_api" {
name = "example-api"
}
resource "aws_api_gateway_resource" "example_resource" {
rest_api_id = aws_api_gateway_rest_api.example_api.id
parent_id = aws_api_gateway_rest_api.example_api.root_resource_id
path_part = "hello-world"
}
resource "aws_api_gateway_method" "example_method" {
rest_api_id = aws_api_gateway_rest_api.example_api.id
resource_id = aws_api_gateway_resource.example_resource.id
http_method = "GET"
authorization = "NONE"
}
resource "aws_api_gateway_integration" "example_integration" {
http_method = aws_api_gateway_method.example_method.http_method
resource_id = aws_api_gateway_resource.example_resource.id
rest_api_id = aws_api_gateway_rest_api.example_api.id
type = "MOCK"
request_templates = {
"application/json" = <<EOF
{"statusCode": 200}
EOF
}
}
resource "aws_api_gateway_method_response" "example_method_response" {
http_method = aws_api_gateway_method.example_method.http_method
resource_id = aws_api_gateway_resource.example_resource.id
rest_api_id = aws_api_gateway_rest_api.example_api.id
status_code = "200"
}
resource "aws_api_gateway_integration_response" "example_integration_respnse" {
http_method = aws_api_gateway_method.example_method.http_method
resource_id = aws_api_gateway_resource.example_resource.id
rest_api_id = aws_api_gateway_rest_api.example_api.id
status_code = aws_api_gateway_method_response.example_method_response.status_code
response_templates = {
"application/json" = <<EOF
{"message": "hello world!"}
EOF
}
}
resource "aws_api_gateway_deployment" "example_deployment" {
rest_api_id = aws_api_gateway_rest_api.example_api.id
stage_description = md5(file("api_gateway.tf"))
lifecycle {
create_before_destroy = true
}
}
resource "aws_api_gateway_stage" "example_stage" {
deployment_id = aws_api_gateway_deployment.example_deployment.id
rest_api_id = aws_api_gateway_rest_api.example_api.id
stage_name = "test"
depends_on = [aws_api_gateway_deployment.example_deployment]
}
To test the setup, get the API URL by checking Stages > test > Invoke URL. While calling the endpoint you should get the similar response to this:
$ curl https://your-invoke-url.execute-api.eu-west-1.amazonaws.com/test/hello-world {"message": "hello world!"}
AWS Cognito
Now, we are going to set up an authorization mechanism protecting our super confidential resources. We will achieve it in two steps.
Firstly, we will build a user pool in Amazon Cognito. The service supports OAuth 2.0’s client credentials grant - so we can use it for our machine-to-machine integration. Within this user pool, we will not have any users, but app clients instead. We are also pointing out the resource server that will be protected by this authorization. Now we need to spin off a few resources with the help of Terraform to make it work. After applying you should see example-integrations user pool created.
resource "aws_cognito_user_pool" "example_user_pool_integrations" {
name = "example-integrations"
account_recovery_setting {
recovery_mechanism {
name = "admin_only"
priority = 1
}
}
admin_create_user_config {
allow_admin_create_user_only = true
}
}
resource "aws_cognito_user_pool_domain" "example_user_pool_integrations_domain" {
domain = "example-user-pool-integrations"
user_pool_id = aws_cognito_user_pool.example_user_pool_integrations.id
}
resource "aws_cognito_user_pool_client" "example_user_pool_integrations_3rd_party_client" {
name = "3rd-party"
user_pool_id
= aws_cognito_user_pool.example_user_pool_integrations.id
access_token_validity = 24
allowed_oauth_flows_user_pool_client = true
allowed_oauth_flows = ["client_credentials"]
allowed_oauth_scopes
= aws_cognito_resource_server.example_user_pool_integrations_resource_sereer.scope_identifiers
enable_token_revocation = true
generate_secret = true
}
resource "aws_cognito_resource_server" "example_user_pool_integrations_resource_server" {
identifier = format("%s%s", aws_api_gateway_deployment.example_deployment.invoke_url, aws_api_gateway_stage.example_stage.stage_name)
name = "Mock API"
user_pool_id = aws_cognito_user_pool.example_user_pool_integrations.id
scope {
scope_name = "hello-world"
scope_description = "Hello world endpoints"
}
}
As stated in configuration, Cognito generates a client secret for a specified app client – for the purpose of generating the token, we need to get the value of the secret. You can do so from the Amazon Cognito console. Open the user pool, then go to App Integration, at the bottom you should see 3rd-party app client - inside you should see the value of client id in plain text and client secret hidden behind show client secret toggle.
What is left is connecting the previously created API with the user pool - we are achieving that by creating an authorizer and configuring the API with it. Let us get back to terraform with API gateway definition. Add authorizer for it:
resource "aws_api_gateway_authorizer" "example_authorizer" {
name = "example-authorizer"
rest_api_id = aws_api_gateway_rest_api.example_api.id
type = "COGNITO_USER_POOLS"
provider_arns = [aws_cognito_user_pool.example_user_pool_integrations.ar
]
} And modify aws_api_gateway_method resource, by specifying alternative authorization type this time and linking it with the authorizer.
resource "aws_api_gateway_method" "example_method" {
rest_api_id = aws_api_gateway_rest_api.example_api.id
resource_id = aws_api_gateway_resource.example_resource.id
http_method = "GET"
authorization = "COGNITO_USER_POOLS"
authorizer_id = aws_api_gateway_authorizer.example_authorizer.id
authorization_scopes = aws_cognito_resource_server.example_user_pool_integrations_resource_server.scope_identifiers
} After applying such changes, the response from API gateway should be different - indicating unauthorized access to the resource as we do not provide any authorization data.
$ curl https://your-invoke-url.execute-api.eu-west-1.amazonaws.com/test/hello-world {"message":"Unauthorized"} To gain the access we need to generate the token.
For this, let us hit the authorization server with basic authorization (specifying client id and secret) scheme, proper grant, and scope:
$ curl -X POST --location "https://example-user-pool-integrations.auth.eu-west-1.amazoncognito.com/oauth2/token" \
-H "Authorization: Basic $(echo -n '<<client id>>:<<client secret>>' | base64)" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=client_credentials&scope=https%3A%2F%2Fyour-invoke-url.execute-api.eu-west-1.amazonaws.com%2Ftest%2Fhello-world"
{"access_token":"...","expires_in":86400,"token_type":"Bearer"} Get the JWT from the access_token field of the response and include it as Authorization header - as a result we are authorized to get the requested resource. We successfully configured authorization based on OAuth2 client credentials flow with the help of Amazon Cognito.
$ curl -X GET --location "https://blwp57a3jd.execute-api.eu-west-1.amazonaws.com/test/hello-world" \
-H "Authorization: Bearer <<access token>>"
{"message": "hello world!"}
Documentation
If you had a chance to get familiar with one of our earlier blog posts, written by Michał Łoza, you know that at Kitopi we utilize OpenAPI with its UI to document HTTP endpoints.
Specification supports OAuth2 as one of security schemes:
"securitySchemes": {
"OAuth2": {
"type": "oauth2",
"name": "oauth2",
"flows": {
"clientCredentials": {
"tokenUrl": "https://example.auth.eu-west-1.amazoncognito.com/oauth2/token",
"scopes": {
"https://example.com/resource": ""
}
}
}
}
} Similarly, Swagger UI provides a way to generate the token and authorize requests with OAuth2
Other authorization methods to consider for M2M integrations
Mutual TLS uses digital certificates to authenticate the client and the server in a communication exchange. With mTLS, both the client and the server must present valid and trusted certificates, ensuring strong mutual authentication. mTLS provides an elevated level of security and integrity by encrypting the communication channel and verifying the identity of both parties. It is well-suited for scenarios where strong and direct client-server authentication is required, without relying on third-party authorization servers or additional access tokens. Consider additional complexity around certificate provisioning, validity, revocation, and key management. Disable default endpoint so your API can be accessed through custom domain name only. For more information on how to configure mutual TLS in API gateway refer to this guide.
Another option, widespread in the context of authorization in machine-to-machine communication is relying on an API key as an authentication token. This is considered insecure as the secret is shared with the resource server directly and it does not provide advanced security features like expiration or revocation. Usage of API keys should be limited to publicly available resources or playing supportive role for enforcing usage policies (throttling, billing). If usage of this type of authentication is constrained by some factors (strict rules imposed by client to be the most common), consider further protecting the resources (i.e., IP whitelisting).
I hope you will find this article helpful when choosing appropriate mechanisms for authorization in machine-to-machine integrations. Worth noting is that authorization is only one of multiple aspects we should consider while protecting our APIs - other factors include IP whitelisting, rate limiting, monitoring and alerting to name a few.



This is a very helpful and well-explained post on machine-to-machine authorization using Amazon Cognito. The way you broke down the concepts makes it much easier to understand, even for readers who are still learning about integrations and security. It’s great to see such clear guidance on an important technical topic.
I often read similar informative content on Education Tips Media, and this article fits perfectly with that style of simple and useful learning. Thanks for sharing such valuable knowledge!
This post is truly impressive and thoughtfully put together. The way you’ve presented your ideas makes it both engaging and easy to follow, which keeps readers interested from beginning to end. It’s always refreshing to come across content that feels both meaningful and well-structured like this.
Adding to the conversation, if you’re someone who enjoys exploring new cultures in a fun and interactive way, Lingo Brights has something worth checking out. Their Korean Name Converter is a simple yet fascinating tool that lets you discover a Korean-style version of your name in just a few clicks. The Korean Name Converter by Lingo Brights makes the experience enjoyable while also giving a small glimpse into Korean naming styles. It’s perfect for anyone curious…
Stunt Bike Extreme I hope you will find this article helpful when choosing appropriate mechanisms for authorization in machine-to-machine integrations.
This is a highly engaging and well-structured post that communicates its message effectively. I like how the information is presented in a balanced way, making it both insightful and easy to follow. The clarity of the writing shows a strong understanding of the topic, and it encourages readers to think beyond surface-level concepts. Content like this contributes meaningfully to professional conversations.
For startups navigating crowded markets, choosing the Digital Marketing Agency for Startups can significantly influence growth outcomes. The right agency helps define brand positioning, optimize digital channels, and create campaigns backed by real data. Instead of guessing what works, startups benefit from strategic planning and measurable execution. Digital marketing becomes a powerful tool when guided by expertise, helping startups establish authority,…
Mình có lần lướt đọc mấy trao đổi trên mạng شيخ روحاني thì thấy nhắc nên cũng tò mò mở ra xem thử cho biết. Mình không tìm hiểu sâu rauhane chỉ xem qua trong thời gian ngắn để quan sát bố cục s3udy cách sắp xếp các mục và trình bày nội dung tổng thể. Cảm giác là các phần được trình bày khá gọn, các mục rõ ràng nên đọc lướt cũng không bị rối Berlinintim, với mình như vậy là đủ để nắm tin cơ bản rồi. q8yat
–