How to Validate Requests in Amazon API Gateway

How to Validate Requests in Amazon API Gateway

In the post, Understanding Amazon API Gateway: Methods and Integrations, we discussed how the method request section manages authorization, API KEY checks, and request validation. We've already covered authorization in articles like How to Secure AWS Lambda Functions Using Amazon API Gateway and AWS IAM and How to Use Lambda Authorizers to Validate Microsoft EntraID (Azure AD) Tokens in Amazon API Gateway. For API KEY checks, see How to Throttle Requests in Amazon API Gateway. Today, we'll focus on the missing piece: request validation.

Pre-requisites

  • An IAM User with programmatic access

  • Install the AWS CLI

  • Install the Amazon Lambda Templates (dotnet new -i Amazon.Lambda.Templates)

  • Install the Amazon Lambda Tools (dotnet tool install -g Amazon.Lambda.Tools)

  • Install AWS SAM CLI

The Lambda function

Run the following commands to set up our Lambda function:

dotnet new lambda.EmptyFunction -n MyLambda -o .
dotnet add src/MyLambda package Amazon.Lambda.APIGatewayEvents
dotnet new sln -n MyApplications
dotnet sln add --in-root src/MyLambda

Open the Program.cs file and update the content as follows:

using Amazon.Lambda.APIGatewayEvents;
using Amazon.Lambda.Core;
using System.Text.Json;
[assembly: LambdaSerializer(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))]
namespace MyLambda;
public class Function
{
    public record RegisterPetRequest(string Name);
    public record RegisterPetResponse(Guid PetId, string Name);
    private readonly JsonSerializerOptions _options;

    public Function()
    {
        _options = new JsonSerializerOptions()
        {
            PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
        };
    }

    public APIGatewayHttpApiV2ProxyResponse FunctionHandler(APIGatewayHttpApiV2ProxyRequest input, ILambdaContext context)
    {
        var request = JsonSerializer.Deserialize<RegisterPetRequest>(input.Body, _options)!;
        var body = JsonSerializer.Serialize(new RegisterPetResponse(Guid.NewGuid(), request.Name), _options);
        return new APIGatewayHttpApiV2ProxyResponse
        {
            Body = body,
            StatusCode = 200,
            Headers = new Dictionary<string, string> { { "Content-Type", "application/json" } }
        };
    }
}

AWS SAM template

At the solution level, create a template.yml file with the following content:

AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: >
  SAM

Resources:
  MyApi:
    Type: AWS::Serverless::Api
    Properties:
      StageName: prod

  MyApiFunction:
    Type: AWS::Serverless::Function
    Properties:
      Timeout: 60
      MemorySize: 512
      Tracing: Active
      Runtime: dotnet8
      Architectures:
        - x86_64    
      Handler: MyLambda::MyLambda.Function::FunctionHandler
      CodeUri: ./src/MyLambda/
      Events:
        Post:
          Type: Api
          Properties:
            RestApiId: !Ref MyApi
            Path: /pets
            Method: post

Outputs:
  MyApiEndpoint:
    Description: "API endpoint"
    Value: !Sub "https://${MyApi}.execute-api.${AWS::Region}.amazonaws.com/prod/pets"

Run the following commands to deploy the resources to AWS:

sam build
sam deploy --guided

Body Validation

We can define a JSON schema as a model that Amazon API Gateway will use to validate that the request body matches this schema. Update the template.yml file as follows:

AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: >
  SAM

Resources:
  MyApi:
    Type: AWS::Serverless::Api
    Properties:
      StageName: prod
      Models:
        RegisterPetRequest:
          type: "object"
          properties:
            name:
              type: "string"
              minLength: 1
          required:
            - name

  MyApiFunction:
    Type: AWS::Serverless::Function
    Properties:
      Timeout: 60
      MemorySize: 512
      Tracing: Active
      Runtime: dotnet8
      Architectures:
        - x86_64    
      Handler: MyLambda::MyLambda.Function::FunctionHandler
      CodeUri: ./src/MyLambda/
      Events:
        Post:
          Type: Api
          Properties:
            RestApiId: !Ref MyApi
            Path: /pets
            Method: post
            RequestModel:
              Model: RegisterPetRequest
              ValidateBody: true

Outputs:
  MyApiEndpoint:
    Description: "API endpoint"
    Value: !Sub "https://${MyApi}.execute-api.${AWS::Region}.amazonaws.com/prod/pets"

The AWS::Serverless::Api resource exposes the Models property, allowing us to define multiple schemas. Then, in the AWS::Serverless::Function under the event source Api, the RequestModel will configure the validation:

  • Model: The name of the model defined earlier.

  • ValidateBody: Enable body validation.

  • ValidateParameters: Enable parameters (path, headers, and query string) validation.

Header And Query String Validation

Amazon API Gateway can validate headers and query string parameters to ensure they are included in the request. Update the AWS::Serverless::Function resource as follows:

  MyApiFunction:
    Type: AWS::Serverless::Function
    Properties:
      Timeout: 60
      MemorySize: 512
      Tracing: Active
      Runtime: dotnet8
      Architectures:
        - x86_64    
      Handler: MyLambda::MyLambda.Function::FunctionHandler
      CodeUri: ./src/MyLambda/
      Events:
        Post:
          Type: Api
          Properties:
            RestApiId: !Ref MyApi
            Path: /pets
            Method: post
            RequestParameters:
             - method.request.header.traceid:
                 Required: true
             - method.request.querystring.version:
                 Required: true
            RequestModel:
              Model: RegisterPetRequest
              ValidateBody: true
              ValidateParameters: true

Within the AWS::Serverless::Function under the event source Api, the RequestParameters will partially set up the validation. We can define multiple request parameters in the following format:

  • method.request.querystring.parameter-name: Represents query string parameters in the URL.

  • method.request.header.parameter-name: Represents HTTP headers sent in the request.

Generally, method.request refers to the incoming request. It allows us to reference different parts of the HTTP request. To complete the configuration, ValidateParameters must be set to true.

If we want to validate only headers and/or query strings, the Model property is still needed, but the ValidateBody property should be set to false.

Gateway Responses

Amazon API Gateway provides a predefined set of responses that automatically handle various errors. When validation fails, Amazon API Gateway returns a 400 status code, and a specific response will be returned depending on the error. In our case, we could expect a BAD_REQUEST_PARAMETERS or BAD_REQUEST_BODY response. These responses, by default, return a short descriptive error message but can be customized based on our needs. Update the AWS::Serverless::Api resource as follows:

  MyApi:
    Type: AWS::Serverless::Api
    Properties:
      StageName: prod
      GatewayResponses:
        BAD_REQUEST_BODY:
          ResponseParameters:
            Headers:
              traceid: "method.request.header.traceid"
          ResponseTemplates:
            application/json: "{\"type\":\"https://example.net/validation-error\" ,\"detail\": \"$context.error.messageString\", \"title\":\"Validation error\" }"
      Models:
        RegisterPetRequest:
          type: "object"
          properties:
            name:
              type: "string"
              minLength: 1
          required:
            - name

In the AWS::Serverless::Api resource, the GatewayResponses property allows us to define multiple response types with the following structure:

  • ResponseParameters: Allows us to specify headers that should be included in the response.

  • ResponseTemplates: Allows us to define the body of the response.

ResponseParameters and ResponseTemplates let us define their content through a template that only supports simple variable substitutions. The template can access $context variable values and $stageVariables property values, as well as method request parameters, in the form of method.request.param-position.param-name.

Conclusions

Performing this kind of validation at the Amazon API Gateway level can eliminate unnecessary calls to the backend, thereby saving money on our bills. Additionally, this information enhances the API definition file, making it easier to understand. Unfortunately, Amazon API Gateway models are defined using the JSON Schema draft #4, which supports only the data types and validations available in this draft. You can find the final code here. Thanks, and happy coding.