How to solve SAM 403 Errors on CORS Preflight

The answer might be easier than you think: your OPTIONS method might be requiring authentication
API Gateway
Picture of John Wright Stanly, author of this blog article
John Wright Stanly
Mar 20, 2021



If your browser's CORS preflight request is responding with a 403, your API's OPTIONS method could be requiring authentication. This might sound okay if your API is authenticated, but the OPTIONS method behaves differently than other HTTP methods. Your API server will need to comply with the CORS standard, which requires OPTIONS methods to work without authentication headers.

How do CORS preflight requests work?


For non-simple HTTP requests, your browser sends a preflight request to check if the server complies with the browser's CORS configuration. The browser completes this preflight with an OPTIONS request to the endpoint with these three headers: Access-Control-Request-Method, Access-Control-Request-Headers, and Origin. The browser does this itself automatically; developers can't manually send the OPTIONS request on the behalf of the browser.

Therefore, it's not possible to add extra headers like x-api-key. If your API is designed to require an API key for OPTIONS methods, it's guarenteed to respond with a 403, even if the actual HTTP request includes the API key in a x-api-key header.

Fixing your API Gateway configuration

Remove any authenticaion requirements found in your OPTIONS method(s).

If you're using the AWS Serverless Application Model (SAM), you know how awesome it can be for creating API's. It can automatically handle CORS and authentication all with just a couple lines of a SAM template.

However, make sure to NOT declare ApiKeyRequired: true inside the Auth property of AWS::Serverless::API. This will require an API key for all methods, including the OPTIONS methods SAM automatically creates to handle CORS.

  Type: AWS::Serverless::Api
    StageName: Prod
      ApiKeyRequired: true #  <-- BAD; CAUSES 403's ON OPTIONS
        CreateUsagePlan: PER_API
        Description: Sample usage plan
          BurstLimit: 10000
          RateLimit: 5000
      AllowMethods: "'*'"
      AllowHeaders: "'*'"
      AllowOrigin: !Sub
        - "'https://www.${Domain}'"
        - Domain: !Ref DomainName
      DomainName: !Join ['.', [api, !Ref DomainName]]
      CertificateArn: !Ref AcmCertificateArn
      EndpointConfiguration: EDGE
        HostedZoneId: !Ref HostedZone

If you want to build an API with SAM that requires an API key for every method, manually define the API key requirement in every AWS::Serverless::Function definition under it's Events property.

  Type: AWS::Serverless::Function
    Handler: handlers/index.sampleFunction
    Description: This is a sample
      - AWSLambdaBasicExecutionRole
        Type: Api
          RestApiId: !Ref SampleAPI
          Path: /endpoint
          Method: GET
            ApiKeyRequired: true #  <-- HERE

Currently, there is no way with SAM to globally require API keys and still satisfy CORS. You also can't standardize API key requirements in the Globals template section because the Events property needs to be unique to every function. Although this might seem like a hassle, authenticating an API with only API keys is not advised. AWS offers plenty other authentication solutions that are more robust. If you're experiencing this problem and only authenticating your API with API keys, it might be time investigate in a beefier authorization solution, such as IAM's or a Lambda authorizer.


Be the first to add a comment!

Add Comment