Protecting a Public REST API in Amazon API Gateway

Figure showing ordinary users that successfully communicate with API Gateway and a malicious user to whom the access is blocked.

In API Gateway, you can use multiple mechanisms to control and manage access to a REST API. For example, using Amazon Cognito user pools, you can maintain a directory of users that have access to your API. Or, using Cognito identity pools, you can integrate with Facebook or Google to provide federated authentication.

If you want to make your API publicly available, you need to let all users invoke your API methods, but, at the same time, to protect the API from malicious users. This blog post describes some simple and cost-efficient ways how to do it.

How to Protect a Public REST API in API Gateway

Throttle API Requests

By default, API Gateway limits the request rate to 10,000 requests per second. To prevent your API from being overwhelmed by too many requests, you may want to set a lower limit in the configuration of an API stage or method.

If you need an example of a CloudFormation template for creating a REST API with custom throttling settings for the POST method, see Implementing a Website Feedback Form Using Amazon API Gateway, SNS, and AWS Lambda.

Ignore Requests Sent by Bots

If your API methods are invoked from a web front end, you may want to embed additional code to the website to integrate it with Google reCAPTCHA v2 or v3. Furthermore, you require that all requests to your API contain a valid reCAPTCHA token.

In this case, you perform the reCAPTCHA verification in the authorizer or integration Lambda function. If the reCAPTCHA token is invalid, or the verification shows that the request is most likely sent by a bot, you do not process it further.

Use DynamoDB to Count Requests per Client IP Address

API Gateway passes the client IP address to the authorizer or integration Lambda function as part of the event object:

{
  "type": "REQUEST",
  ...
  "requestContext": {
    ...
    "identity": {
      ...
      "sourceIp": "1.2.3.4"
    },
    ...
  }
}

Having the client IP address in a Lambda function, you can use a DynamoDB table and DynamoDB atomic counters to store and increment the number of requests received from this IP address. The DynamoDB table has the IP address as a partition key. For example, you can create such a table with the following AWS CLI command:

aws dynamodb create-table \
    --table-name ip-log \
    --attribute-definitions AttributeName=Ip,AttributeType=S \
    --key-schema AttributeName=Ip,KeyType=HASH \
    --billing-mode=PAY_PER_REQUEST

In one call to the DynamoDB service, you can increment in the DynamoDB table the number of requests received from the client IP address, get the current value of this counter, and the timestamp from which you started counting the requests. For example, for the IP address 1.2.3.4, the AWS CLI presentation of this operation would be the following:

aws dynamodb update-item \
    --table-name ip-log \
    --key '{"Ip": {"S": "1.2.3.4"}}' \
    --update-expression 'SET InsertTimestamp = if_not_exists(InsertTimestamp, :ts),
                             RequestsCount = if_not_exists(RequestsCount, :zero) + :one' \
    --expression-attribute-values "{\":ts\": {\"S\": \"$(date -u '+%Y-%m-%dT%H:%M:%SZ')\"},
                                    \":zero\": {\"N\": \"0\"}, 
                                    \":one\": {\"N\": \"1\"}}" \
    --return-values UPDATED_NEW

You may decide, for example, that you allow only 100 requests per IP address in 24 hours.

If the current time (the time when the Lambda function is invoked) is less than the returned InsertTimestamp plus 24 hours, and the returned RequestsCount is less than or equal to 100, then the client request should be accepted. If the current time is less than the returned InsertTimestamp plus 24 hours, and the returned RequestsCount exceeds the threshold of 100, then the client request should be terminated. In this case, you may want to return an application error or even block the client IP address on the firewall side.

If the current time is more than or equal to the returned InsertTimestamp plus 24 hours, then you need to reset the counter:

aws dynamodb update-item \
    --table-name ip-log \
    --key '{"Ip": {"S": "1.2.3.4"}}' \
    --update-expression 'SET InsertTimestamp = :ts, RequestsCount = :one' \
    --expression-attribute-values "{\":ts\": {\"S\": \"$(date -u '+%Y-%m-%dT%H:%M:%SZ')\"},
                                    \":one\": {\"N\": \"1\"}}" \
    --return-values UPDATED_NEW

In this update operation, you additionally may want to add --condition-expression to avoid that the counter is reset multiple times from simultaneous Lambda function executions. For further details, see the information about DynamoDB conditional updates.

Besides the number of the requests for each client IP address, you can store in the DynamoDB table other metrics, and make decisions based on them. For example, you may want to store the average Google reCAPTCHA v3 score and make the allowed requests threshold dependent on this score.

Use AWS WAF

AWS WAS is a web application firewall that you can use to protect your REST API. In AWS WAF, you create a web access control list (web ACL) with different rules.

You can add a rate-based rule that tracks the rate of requests for each originating IP address and block all additional requests when the rate is exceeded. You set the limit as the number of allowed requests per 5 minutes. For example, to mitigate low-volume application threats, you can set the limit to 100 requests per 5 minutes.

Also, you can use managed rule groups maintained by AWS or AWS Marketplace sellers. For example, the following AWS rules:

  • Amazon IP reputation list to block sources associated with bots or other threats
  • Anonymous IP list to block requests from services that allow obfuscation of viewer identity (requests originating from VPN, proxies or Tor nodes, etc.)

In addition, you can create an IP set with your own blacklist of IP addresses. You can update entries of this list from your authorizer or integration Lambda function.

For further information about AWS WAF, refer to the AWS documentation.

  • A combination of different mechanisms, including those described in this post, are successfully used to secure access to the public face detection service of the online image privacy editor https://picdefacer.com.