How to configure an AWS API Gateway with Cognito using OpenAPI and Terraform

Piotr Kleban
9 min readJun 9, 2023

--

Using an API gateway such as Amazon API Gateway allows you to create a single entry point for your microservices and apply features such as:

  • authentication,
  • rate limiting,
  • caching,
  • monitoring

Table of contents:

API Gateway: A Key concepts and BFF pattern
· AWS API Gateway — pros and cons
· ALB and WAF: An alternative solution
· AWS API gateway advantages against ALB and WAF
Steps for creating AWS API gateway with OpenAPI spec
· Terraform templatefile for OpenAPI generation
· Cognito with OpenAPI authorizer AWS extension
Conclusions

API Gateway: A Key concepts and BFF pattern

You can use an API gateway when:

  • You want to provide a single point of entry and a consistent interface for your APIs or microservices. An API gateway acts as a facade and lets you move or modify the backend components with no impact to the client.
  • You want to apply security policies, authentication mechanisms, rate limiting, and other features to your APIs or microservices.
  • You want to monitor, log, and analyze the usage and performance of your APIs or microservices.
  • Enforce authorization and throttling to protect your microservices

An API Gateway is a service that acts as a front-end for back-end services. It helps to simplify and standardize the access to your services. However, a single API Gateway may not be able to handle all the needs of different clients or features. When developing an application for different platforms, such as desktop and mobile, the backend service can face many challenges. The frontend requirements can vary depending on the device capabilities, screen size, and performance. This can lead to a complex and constantly changing backend that tries to satisfy all the frontend needs. Moreover, the backend can become a bottleneck and a source of conflict for different frontend teams that depend on it.

For example, the web UI may offer more options and filters for browsing products, while the mobile app may offer more features for scanning barcodes. Or mobile app may need less data and faster responses than a web UI. Another thing, it may have to deal with different data formats, performance requirements, security policies. These challenges can result in a complex, bloated, and hard-to-maintain backend service that tries to satisfy all the frontend competing requirements.

That’s why it may be needed to use Backend for Frontend (BFF), which is a pattern that uses multiple API Gateways for different clients. For example, there can be a BFF for mobile clients and another BFF for web clients. This way, it is possible to customize each BFF to the specific needs of each client or feature. Each API gateway can be optimized for its specific frontend needs and reduce the coupling between the frontend and the backend. Each frontend team can have autonomy and flexibility to manage their own API gateway without relying on a centralized backend team

AWS API Gateway — pros and cons

AWS API Gateway is an proprietary implementation of an API Gateway created by Amazon Web Services. It is a service that allows you to create, publish, maintain, monitor and secure APIs at any scale.

The main factors to consider when selecting an API gateway is that AWS API gateway provides:

  • Easy integration with Amazon Cognito, allowing you to control who can access your APIs and how.
  • It is managed (can handle high volumes of concurrent requests and provide low latency and high availability, can scale up or down automatically)
  • Allows setting up rate limits, usage plans
  • It supports both stateful (WebSocket API type) and stateless (HTTP and REST) APIs, enabling you to build real-time two-way communication applications as well as standard web applications.
  • It provides tools for testing, monitoring and logging the APIs — integrates with CloudTrail and CloudWatch for logging and monitoring of API usage, including the ability to set alarms.

Disadvantages of AWS API gateway against other API gateway tools

There are many API gateway tools available in the market, such as Kong Gateway, Tyk or Gloo.

API gateway is a service that offers many benefits (like being managed services), but it also has some drawbacks against other tools:

  • AWS API gateway is tied to AWS and cannot run on different cloud platforms or providers
  • AWS API gateway provides vendor lock-in and makes it hard to switch to another API gateway tool or solution
  • AWS API gateway is not open source and does not allow customizations or extensions

ALB and WAF: An alternative solution

When building an API you need a way to manage and route the incoming traffic to your backend resources. AWS offers two main options for this purpose: AWS API Gateway and Application Load Balancer (ALB). Both services can handle HTTP requests, integrate with AWS Lambda functions, and support WebSocket connections.

However, fundamentally both are completely different concepts.

An API gateway and a load balancer are both tools that manage traffic between clients and servers, but they have different roles and functions. An API gateway perform tasks like authentication, rate limiting, logging, etc. A load balancer handles the distribution of incoming traffic across multiple servers.

AWS API Gateway can be replaced by ALB in some cases

At the moment, ALB supports so many features that it satisfy many needs:

  • ALB also integrates with Cognito/JWT
  • integrates with Lambda and other AWS services
  • supports Path-based routing: You can use ALB to route requests based on the path of the URL. For example, you can route requests with /api/* path to a group of Lambda functions that handle your logic.
  • supports Host-based routing: You can use ALB to route requests based on the host header of the request. For example, you can route requests with api.example.com host header to a group of EC2 instances that serve your content.
  • supports Query string parameters routing: You can use ALB to route requests based on the query string parameters of the request. For example, you can route requests with ?version=2 query string parameter to a group of ECS containers that run a newer version of your application.
  • supports HTTP method routing: You can use ALB to route requests based on the HTTP method of the request. For example, you can route GET requests to a group of Lambda functions that perform read operations on your database.

ALB combined with WAF enables rate limiting.

If you need more basic features, ALB might be a better option than AWS API Gateway because it provides many features out of the box without any additional maintenance complexity of API gateway and its cost.

AWS API gateway advantages against ALB and WAF

  • AWS API gateway provides more features for managing and securing APIs, such as authentication and authorization mechanisms (API keys, IAM roles and policies, Cognito user pools, Lambda authorizers, JWT tokens), throttling and quotas (per second or per month limits for individual endpoints or for the whole API)
  • AWS API Gateway can be used with OpenAPI specification
  • Can be cheaper. ALB and API Gateway have different pricing models. ALB charges by hours and LCUs (Load Balancer Capacity Units). API Gateway charges by requests and data transfer.

Steps for creating AWS API gateway with OpenAPI spec

We need to use the following resources:

  • aws_apigatewayv2_api: key component, we can specify the openapi spec in the body argument of this resource. This will define the API endpoints, methods, parameters, responses.
  • aws_apigatewayv2_stage: A stage is state of your API (for example: dev, prod, beta, v1).
  • aws_apigatewayv2_deployment: A deployment is a snapshot of the API that can be accessed through a stage. To associate a deployment with a stage, you need to specify the deployment_id argument of the aws_apigatewayv2_stage resource.

Before creating aws_apigatewayv2_deployment, we must have the following resources:

  • aws_apigatewayv2_route: A route is a rule that defines how API Gateway handles requests for a specific resource path. We need to specify the target argument of this resource to reference an integration, such as a lambda function or an HTTP endpoint.
  • aws_apigatewayv2_integration: An integration connects an API route with a backend service. You need to specify the api_id argument of this resource to reference the aws_apigatewayv2_api resource. You also need to specify the integration_type argument of this resource to indicate the type of integration, such as AWS_PROXY or HTTP_PROXY.

The good news is we do not need to create Terraform aws_apigatewayv2_integration and aws_apigatewayv2_route resources in tf file if you use openapi file. We can specify the integration and route information in the openapi spec using a nice AWS feature —API Gateway extensions to OpenAPI.

Extension x-amazon-apigateway-integration allows you to define the type, method, uri, and other settings for the integration. You can also use the x-amazon-apigateway-any-method object to define a catch-all route for any HTTP method.

The x-amazon-apigateway-integration object can be either referenced or inline (specified directly in /device GET in the following example), depending on the type of API and the preference of the user. Inline integration :

# API example with inline integration
paths:
/device:
get:
# Use the x-amazon-apigateway-integration extension to integrate with lambda function
x-amazon-apigateway-integration:
type: AWS_PROXY
httpMethod: POST
uri: <lambda>
passthroughBehavior: when_no_match
contentHandling: CONVERT_TO_TEXT
payloadFormatVersion: 2.0
responses:
default:
description: Default response for GET /example

Object x-amazon-apigateway-integration can be defined in the components section of the openapi file, and then referenced by using $ref within each path and method object. This allows reusing the integrations for multiple routes. For example:

# API example with referenced integration
paths:
/device:
get:
# Reference the integration defined in the components section
x-amazon-apigateway-integration:
$ref: '#/components/x-amazon-apigateway-integrations/integration1'
components:
# Define a collection of integrations
x-amazon-apigateway-integrations:
integration1:
type: AWS_PROXY
httpMethod: POST
uri: <lambda>
passthroughBehavior: when_no_match
contentHandling: CONVERT_TO_TEXT
payloadFormatVersion: 2.0

Terraform templatefile for OpenAPI generation

Terraform provides a way to generate OpenAPI from template by using the templatefile function. This function allows you to render a yaml file with placeholders that are replaced by variables passed from Terraform.

# This is the openapi.yaml file with placeholders
openapi: 3.0.0
info:
title: example-api
version: v1
paths:
/example:
get:
# Use the x-amazon-apigateway-integration extension to integrate with lambda function
x-amazon-apigateway-integration:
type: AWS_PROXY
httpMethod: POST
# Use the variable passed from the templatefile function
uri: ${lambda_invoke_arn} # <- THIS PLACEHOLDER WILL BE FILLED
payloadFormatVersion: 2.0
timeoutInMillis: <time>

resource "aws_apigatewayv2_api" "example" {
name = "example-api"
protocol_type = "HTTP"
body = templatefile("${path.module}/openapi.yaml", {
# Pass the lambda function invoke arn as a variable
lambda_invoke_arn = aws_lambda_function.example.invoke_arn
})
}

Note: it is recommended to validate OpenAPI file!

Online tools such as https://editor.swagger.io/, can check the syntax and semantics of your yaml file and show any errors or warnings. Alternatively, you can use command line tools such as spectral.

We also need to allow API Gateway to invoke a Lambda function when a client calls an API endpoint. Example usage ofaws_lambda_permissionresource usage is here.

Cognito with OpenAPI authorizer AWS extension

Another comfortable OpenAPI AWS extension is “x-amazon-apigateway-authorizer” that can be used in order to authorize access. It validates and authorizes a JSON Web Token (JWT) based on its issuer and audience claims. The “x-amazon-apigateway-authorizer” extension can be used in the security definition or security scheme of an OpenAPI document to specify the type, configuration, and identity source of the authorizer.

openapi: 3.0.0
info:
title: Sample API
description: api description here
version: v1
paths:
/example:
get:
security: # This is where you apply the authorizer to the API endpoint
- jwt-authorizer: [] # This is the name of the authorizer defined below
x-amazon-apigateway-integration:
type: AWS_PROXY
(...)
components:
securitySchemes:
jwt-authorizer: # This is the custom name of the authorizer that you can reference in the security section above
type: oauth2
x-amazon-apigateway-authorizer:
type: jwt
identitySource: "$request.header.Authorization" # This indicates that the JWT token is passed in the Authorization header of the request
jwtConfiguration:
audience:
- ${client_id} # This is where you specify the client ID of your Cognito user pool app client
issuer: https://cognito-idp.${region}.amazonaws.com/${user_pool_id} # This is where you specify the issuer of your Cognito user pool JWT token

To apply security to all endpoints, you need to use the security keyword on the root level of your OpenAPI document. This keyword takes an array of security requirement objects that specify which security schemes are applied to all API operations by default. Each security requirement object is a map between a security scheme name and an array of scopes. For example, if you have defined a security scheme named jwt-authorize in the “components/securitySchemes” section, you can apply it to all endpoints by adding the following security section on the root level:

openapi: 3.0.0
info:
title: Sample API
description: api description here
version: '0.1'
security:
- jwt-authorizer: []

This means that all API operations require JWT jwt-authorizer authentication by default.

In order to extract audience and issuer we can use aws-cli :

region=eu-west-1
pool_id=eu-west-1_AbcDEFGHi
# list all client IDs:
aws cognito-idp list-user-pool-clients - user-pool-id ${pool_id} | jq -r .UserPoolClients[].ClientId
# print issuer URL:
echo "https://cognito-idp.${region}.amazonaws.com/${pool_id}"

Conclusions

OpenAPI AWS extensions “x-amazon-apigateway-authorizer” and “x-amazon-apigateway-integration” can configure the authorization and integration details of an API Gateway API. The “x-amazon-apigateway-authorizer” extension defines a JWT authorizer that validates and authorizes the requests to the API.

The authorizer validates and authorizes the JWT token based on its audience and issuer claims.

The Lambda function can access the claims in the JWT token through the event.requestContext.authorizer.jwt.claims object.

The “x-amazon-apigateway-integration” extension specifies the backend integration type, method, URI, parameters, and other options for the API.

Thank you.

--

--

Piotr Kleban

Wizard of automation. Makes sure that code does not explode when it goes live. Obsessed with agile, cloud-native, and modern approaches. # x.com/PiotrKlebanDev