David Hang

Any sufficiently advanced technology is indistinguishable from magic - Arthur C. Clarke
< Previous - Next >

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.

< Previous - Next >
< Previous - Next >