AWS Lambda SAM CORS
I leave this post more as a reminder to myself, and hopefully to help any others that might be struggling with CORS when deploying AWS Lambdas using SAM.
I was working on a personal project to help my wife out with extracting data from PDF invoices she was receiving. I developed the logic to extract the data and wanted to try deploying it to an AWS Lambda to allow it be used in various ways.
- An automation (google app script) which scans emails, archive pdfs and extracting out the data to a google sheet. This is still a work in progress.
- A basic web frontend that allowed less technical users (CLIs can be scary) to upload their invoices and allow them to download a csv of the extracted data.
I used AWS SAM (Serverless Application Model) to deploy the Lambdas and the API Gateway, which is essentially an Infrastructure as Code (IaC) tool for AWS serverless applications. Awesome I can ping the endpoint with Insomnia an API client and get my expected data. I started developing a frontend application with Dropzone.JS to allow users to select and upload their invoices. I tried it out, and âcomputer says noâ.
Cross-Origin Request Blocked: The Same Origin Policy disallows reading the
remote resource at https://XXX.execute-api.XXX.amazonaws.com/Prod/XXX/.
(Reason: CORS header âAccess-Control-Allow-Originâ missing). Status code: 502.
For security reasons, browsers (but not other consumers of apis) block access to resources not from the same origin (essentially website) unless the resource specifically allows that via Cross-Origin Resource Sharing (CORS) headers. Cool cool cool, I just need to return the CORS headers, that shouldnât be too hard, people have probably done this a million times before with AWS Lambdas and SAM.
Surprisingly, I couldnât find a good example of it. A lot of sources say to set
it on the API Gateway via the template.yaml
.
Globals:
Function:
Timeout: 3
Api:
Cors:
AllowOrigin: "'*'"
AllowMethods: "'POST, GET, PUT, DELETE'"
AllowHeaders: "'X-Forwarded-For, Content-Type'"
Whilst when deployed this looked like it set up a mock response on the API Gateway to return those headers when an OPTION request was sent, it didnât work, I still got a CORS error.
After a bit of more searching, reading and experimenting I found the solution. I
think since SAM sets up the Lambda with the API Gateway using a Lambda proxy
integration, all requests to the endpoint are proxied to the Lambda, the request
dropped and the mock response is completely ignored. I needed to include an
OPTIONS
method in the template.yaml
for the endpoint.
AWSTemplateFormatVersion: "2010-09-09"
Transform: AWS::Serverless-2016-10-31
Description: >
XXX
Globals:
Function:
Timeout: 3
Resources:
XXXFunction:
Type: AWS::Serverless::Function
Properties:
FunctionName: function-name
CodeUri: XXX/
Handler: app.lambda_handler
Runtime: python3.12
Timeout: 60
Architectures:
- x86_64
Events:
XXXEvent:
Type: Api
Properties:
Path: /XXX
Method: post
Cors:
Type: Api
Properties:
Path: /XXX
Method: options
In the Lambda, I then needed to add the options response to the Lambda handler.
def lambda_handler(event: dict, context: LambdaContext) -> dict[str, str | int]:
if event.get("httpMethod") == "OPTIONS":
return {
"statusCode": 200,
"headers": {
"Access-Control-Allow-Headers": "*",
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "POST",
},
}
# rest of the lambda handler
And success!
I donât know if this is the best way to do it, but it works! Hopefully this helps out anyone else who is struggling with CORS when deploying Lambdas using SAM.