6
votes

I'm working to get a static website to call API gateway using CORS. I've got the code below running using SAM local, but I'm getting the following CORS error trying to call my API with jQuery from my static website (hosted locally):

Failed to load http://localhost:3000/notify: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:4000' is therefore not allowed access.

My template.yaml:

AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31

Globals:
  Api:
    # enable CORS; to make more specific, change the origin wildcard
    # to a particular domain name, e.g. "'www.example.com'"
    Cors: "'*'"

Parameters:
  RecaptchaSecret:
    Type: String

Resources:
  NotifierFunction:
    Type: AWS::Serverless::Function 
    Properties:
      CodeUri: notifier/build
      Handler: app.lambda_handler
      Runtime: python3.6
      Environment: 
        Variables:
          PARAM1: VALUE
      Events:
        Notify:
          Type: Api 
          Properties:
            Path: /notify
            Method: post

Outputs:
    NotifierApi:
      Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/notify/"
    NotifierFunction:
      Value: !GetAtt NotifierFunction.Arn
    NotifierFunctionIamRole:
      Value: !GetAtt NotifierFunctionRole.Arn

My understanding of the Globals section in SAM is that it should apply the Api field to my (inferred) API gateway.

I've seen a few examples of CORS with API gateway online where others have utilized standard API gateway templates, and some where people have used SAM in addition to a swagger file, but I've been unable to see a successful example of someone getting CORS to work using SAM without a swagger file (see below references). I feel like I must be missing something obvious!

I'm using a regular POST request from jQuery, I can post my front end code, or the "compiled" CloudForamtion as well if it's helpful.

Any help is very appreciated!

Cheers :)

References I've looked at:

Here is my function code:

import json
import boto3
import requests

def lambda_handler(event, context):
    print "REACHED"
    print event
    ip = requests.get('http://checkip.amazonaws.com/')

    return {
        "statusCode": 200,
        "headers": {"Access-Control-Allow-Origin": "*"},
        "body": json.dumps({
            'message': 'hello world',
            'location': ip.text.replace('\n', ''),
        })
    }
1
I don't know much about that way of creating the template, could you show me the function code, specifically what response it returns. Basically I'd always return the correct headers from the function.Mrk Fldig
Hey! I added the function code. Basically just the hello-world with the CORS thing added.curiouscat
Hrmm that's odd, in APIG whats the integration type please? So basically go into the console, click api, click the method, is it Lambda Proxy?Mrk Fldig
Hey! Sorry it took so long to get back to you. The integration type (under "Integration Request") is in fact "Lambda Proxy".curiouscat
This sam template doesn't add the "Access-Control-Allow-Origin" to your OPTIONS ressource. Add it and male sure that your lambda returns the Access-Control-Allow-Headers" header correctly.MarcJohnson

1 Answers

0
votes

If your still interested in how to achieve this, can be done directly through the cloudformation template as per the below, its best if you define a root mock resource to begin with before your post child resource.

  MockMethod:
    Type: 'AWS::ApiGateway::Method'
    Properties:
      AuthorizationType: NONE
      RestApiId: STRING
      ResourceId: STRING
      HttpMethod: OPTIONS
      Integration:
        Type: MOCK
        IntegrationResponses:
          - StatusCode: 200 
            ResponseParameters:
              method.response.header.Access-Control-Allow-Headers: "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token'"
              method.response.header.Access-Control-Allow-Methods: "'POST,OPTIONS'"
              method.response.header.Access-Control-Allow-Origin:* #Note * allows all origins
            SelectionPattern: 2\d{2}
            ResponseTemplates: 
              application/json: Empty
        PassthroughBehavior: WHEN_NO_MATCH
        RequestTemplates:
          application/json: '{"statusCode": 200}'
      MethodResponses:
        - ResponseModels:
            application/json: Empty
          ResponseParameters:
            method.response.header.Access-Control-Allow-Headers: true
            method.response.header.Access-Control-Allow-Methods: true
            method.response.header.Access-Control-Allow-Origin: true
          StatusCode: 200

Note, OPTIONS is what handles your preflight CORS request prior to POST method. Additionally if you are using AWS_PROXY as per below you need to handle the header CORS response in your lambda function.

  POSTmethod: 
    Type: AWS::ApiGateway::Method 
    Properties:  
      HttpMethod: POST 
      RestApiId: STRING
      ResourceId: STRING
      Integration: 
        Type: AWS_PROXY 
        ConnectionType: INTERNET 
        IntegrationHttpMethod: POST 
        Credentials: STRING
        Uri: STRING
        PassthroughBehavior: WHEN_NO_TEMPLATES
        TimeoutInMillis: 10000 #Timeout in 10seconds  
      MethodResponses:
        - StatusCode: 200
          ResponseModels:
            application/json: Empty
          ResponseParameters:
              method.response.header.Access-Control-Allow-Headers: true
              method.response.header.Access-Control-Allow-Origin: true
        - StatusCode: 400
          ResponseModels:
            application/json: Empty

  OPTIONSmethod:
    Type: 'AWS::ApiGateway::Method'
    Properties:
      AuthorizationType: NONE
      RestApiId: STRING
      ResourceId: STRING
      HttpMethod: OPTIONS
      Integration:
        Type: MOCK
        IntegrationResponses:
          - StatusCode: 200 
            ResponseParameters:
              method.response.header.Access-Control-Allow-Headers: "'Content-Type,X-Amz-Date,Authorization,x-api-key,X-Amz-Security-Token'"
              method.response.header.Access-Control-Allow-Methods: "'POST,OPTIONS'"
              method.response.header.Access-Control-Allow-Origin: "'*'"
            SelectionPattern: 2\d{2}
            ResponseTemplates: 
              application/json: Empty
        PassthroughBehavior: WHEN_NO_MATCH
        RequestTemplates:
          application/json: '{"statusCode": 200}'
      MethodResponses:
        - ResponseModels:
            application/json: Empty
          ResponseParameters:
            method.response.header.Access-Control-Allow-Headers: true
            method.response.header.Access-Control-Allow-Methods: true
            method.response.header.Access-Control-Allow-Origin: true
          StatusCode: 200