Request Validation for AWS Lambda using OpenApi
Whenever a public API – accessed by many users – is developed, it is common to validate the request. This is a core feature and it is done to prevent unexpected behavior and ensure reliability of the API.
Request validation introduces more source code into the system. The more the validation rules are complex, the more the code required. In a NodeJS express system, packages such as Joi, Yup etc can be used for this purpose. These packages can also be used in lambda functions ( NodeJs ). Then why do we need OpenAPI ?
OpenAPI
OpenAPI defines a standard interface to RESTful APIs which helps in understanding the capabilities of the service without access to source code, documentation, or through network traffic inspection.
Using OpenAPI, we can validate the request without invoking the lambda function. This means, we can separate the request validation from lambda so that our lambda can now purely concentrate on the business logic. Let’s see how it works.
API Gateway
API Gateway is the key to integrating REST, HTTP, and WebSocket with other aws services. So in our case, when an API call happens, it initially reaches the API gateway, then it gets forwarded to our lambda function. Thus executes the business logic and so on. As said earlier, Open API allows us to validate the request before reaching the lambda. That means our request gets validated right at the API Gateway.
Using Open API, we can customize the responses returned by the API Gateway for requests that are not successfully forwarded to the integration backend.
What we will do
Let’s build an API, using a SAM template, that accepts POST requests. The SAM template snippet would be in the following format.
MyLambdaFunction: Type: AWS::Serverless::Function Properties: CodeUri: /path/to/source Events: eventPOST: Type: Api Properties: RestApiId: !Ref MyApiGateway Path: /v1/data Method: POST MyApiGateway: Type: AWS::Serverless::Api OpenApiVersion: “3.0.1” GatewayResponses: BAD_REQUEST_BODY: StatusCode: 400 ResponseTemplates: application/json: “{\”errorText\”: \”$context.error.validationErrorString\”, \”correlationId\”: \”$context.requestId\”, \”statusCode\”: 400, \”data\”: {\”externalReferenceId\”: $input.params(‘correlationid’)}}” DefinitionBody: Fn::Transform: Name: AWS::Include Parameters: Location: open-api.yaml LambdaExecutionRole: Type: “AWS::IAM::Role” Properties: AssumeRolePolicyDocument: Version: 2012-10-17 Statement: – Effect: Allow Principal: Service: – apigateway.amazonaws.com Action: – “sts:AssumeRole” Policies: – PolicyName: AllowLambdaExecution PolicyDocument: Version: 2012-10-17 Statement: – Effect: Allow Action: – “lambda:InvokeFunction” Resource: !GetAtt MyLambdaFunction.Arn |
The above template snippet creates and deploys a lambda function named “MyLambdaFunction”, execution role named “LambdaExecutionRole” and an API Gateway named “MyApiGateway”.
It is shown how the ‘BAD_REQUEST_BODY’ response from Api Gateway is customized in the SAM template. The ‘DefinitionBody’ property specifies the OpenApi template to be used. The following snippet shows a sample Open Api definition to validate request body and integrate with AWS Lambda function.
openapi: “3.0.1” info: title: “Rest Api Gateway Lambda Integration” description: “Open Api integration for API Gateway and aws lambda” version: “2021-12-28T06:39:26Z” paths: /v1/data: post: requestBody: description: Post API content: application/json: schema: $ref: “#/components/schemas/MyRequestBody” responses: “201”: description: “201 response” “500”: description: “500 response” x-amazon-apigateway-integration: httpMethod: “POST” passthroughBehavior: “when_no_match” type: “aws_proxy” credentials: Fn::GetAtt: “LambdaExecutionRole.Arn” uri: Fn::Sub: “arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyLambdaFunction.Arn}/invocations” x-amazon-apigateway-request-validator: “body-validator” x-amazon-apigateway-auth: type: “AWS_IAM” x-amazon-apigateway-request-validators: body-validator: validateRequestParameters: false validateRequestBody: true components: schemas: MyRequestBody: type: object properties: userId: type: string format: uuid name: type: string minLength: 1 organizationNumber: type: string type: type: integer minimum: 1 maximum: 3 organizationId: type: integer nullable: true required: – “userId” – “name” – “type” – “organizationNumber” |
An OpenAPI template starts with the version specified by the property ‘openapi’. The ‘info’ is optional to describe the definition. The ‘paths’ field is required and includes the method accepted as well as backend integration is specified here. The http requests can be validated based on a ‘schema’ which is specified in the ‘components’ section.
‘X-amazon-apigateway-integration’ object specifies the backend integration used for the method. It extends the OpenAPI object. In Order to integrate a backend service, the uri of the service should be defined in the ‘uri’ field of the integration object. A lambda function uri takes the following format :
“arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyLambdaFunction.Arn}/invocations” |
The lambda execution role needs to be specified in the ‘credentials’ to grant permissions to execute the lambda. Lambda execution role should also allow api gateway service in its ‘AssumeRolePolicyDocument’.
Conclusion
Open API enables to shorten the lambda function source code, makes lambda functions focus purely on business logic and makes code more debuggable .