David Hang

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

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
Previous