11

I'm trying to enable CORS in my AWS SAM app. Here is the snippet from my template.yaml:

Globals:
  Api:
    Cors:
      AllowMethods: "'*'"
      AllowHeaders: "'*'"
      AllowOrigin: "'*'"

Resources:
  MyApi:
    Type: AWS::Serverless::Api
    Properties:
      StageName: prod
      Auth:
        Authorizers:
          MyCognitoAuthorizer: ...

  getByIdFunc:
    Type: AWS::Serverless::Function
    Properties:
      Handler: src/handler.handle
      Events:
        ApiEvent:
          Type: Api
          Properties:
            Path: /{id}
            Method: GET
            RestApiId: !Ref MyApi

According to this Using CORS with AWS SAM and that https://github.com/aws/serverless-application-model/issues/373, the cors config should work but unfortunately no header is set on the API response, as seen below.

< HTTP/2 200 
< content-type: application/json
< content-length: 770
< date: Tue, 13 Apr 2021 19:55:31 GMT
< x-amzn-requestid: ...
< x-amz-apigw-id: ...
< x-amzn-trace-id: Root=1-...-...;Sampled=0
< x-cache: Miss from cloudfront
< via: 1.1 ...cloudfront.net (CloudFront)
< x-amz-cf-pop: FRA2-C2
< x-amz-cf-id: ...==
< 
* Connection #0 to host ....execute-api.eu-central-1.amazonaws.com left intact
[{"model": ..}]

I also tried adding the cors config to the API definition (MyApi) itself like its stated in the offical docs here, but without success.

I could add the header in the response by myself but i rather have it in the template file.

Jan
  • 453
  • 1
  • 5
  • 13
  • Can you provide your API response in the code? – Robert Kossendey Apr 12 '21 at 20:20
  • Are you testing api from browser or postman? Can you share response. – nirvana124 Apr 13 '21 at 06:26
  • I updated my question with the response. @PankajYadav I tried both. The browser throws an error complaining about the missing cors header and in postman the header is also missing. The above output is from cUrl – Jan Apr 13 '21 at 20:13
  • Does this answer your question? [Using CORS with AWS SAM](https://stackoverflow.com/questions/50229563/using-cors-with-aws-sam) – petey Apr 15 '21 at 18:58
  • no this was actually more confusing for me because according to this comment https://stackoverflow.com/a/50229974/7918330, the CORS config I made like i showed above should work, but i did not. – Jan Apr 16 '21 at 19:06
  • i now added the cors header in the response myself. i didnt find a better solution – Jan Apr 16 '21 at 19:06

6 Answers6

17

What solved it for me was adding the following to my template.yaml:

Globals:
    Api:
        Cors:
            AllowMethods: "'GET,POST,OPTIONS'"
            AllowHeaders: "'content-type'"
            AllowOrigin: "'*'"
            # AllowCredentials: true  Uncomment only if you choose a specific origin instead of the * wildcard.

And just like nirvana124 and Nitesh said, you also need to return these headers with the response in each endpoint:

return {
    statusCode: 200,
    headers: {
        "Access-Control-Allow-Headers" : "Content-Type",
        "Access-Control-Allow-Origin": "*", // Allow from anywhere 
        "Access-Control-Allow-Methods": "GET" // Allow only GET request 
    },
    body: JSON.stringify(response)
}
isaacsan 123
  • 1,045
  • 8
  • 11
9

Using SAM/CLoudformation or AWS Console you can setup CORS headers for OPTIONS method which will be called by browser before calling your actual API method(GE/POST etc). From postman or any other service only your endpoint will be called.

When we are using lambda proxy with API Gateway we need to set the CORS headers in the response object from lambda.

To enable CORS for the Lambda proxy integration, you must add Access-Control-Allow-Origin:domain-name to the output headers. domain-name can be * for any domain name.

return {
        statusCode: 200,
        headers: {
            "Access-Control-Allow-Headers" : "Content-Type",
            "Access-Control-Allow-Origin": "*", // Allow from anywhere 
            "Access-Control-Allow-Methods": "GET" // Allow only GET request 
        },
        body: JSON.stringify(response)
    }
nirvana124
  • 903
  • 1
  • 9
  • 12
4

For anyone using API Gateway Version 2 (HttpApi), it doesn't have support for 'AddDefaultAuthorizerToCorsPreflight', at least not specified in their documentation.

Instead (per AWS guidelines), we need to add a route in lambda function for OPTIONS /{proxy+} and turn off Authentication for this route.

MyHttpApi:
 Type: AWS::Serverless::HttpApi
 Properties:
  Auth:
    DefaultAuthorizer: OAuth2
    Authorizers:
      OAuth2:
        JwtConfiguration:
          issuer: "..."
          audience:
            - ...
        IdentitySource: "$request.header.Authorization"
  CorsConfiguration:
    AllowOrigins:
      - "*"
    AllowMethods: 
      - GET
      - POST
      - OPTIONS
    AllowHeaders:
      - Content-Type
      - Accept
      - Access-Control-Allow-Headers
      - Access-Control-Request-Method
      - Access-Control-Request-Headers
      - Authorization
MyLambdaFunction:
 Type: AWS::Serverless::Function
 Properties:
  Handler: index.handler
  Events:
    CorsPreflightEvent:
      Type: HttpApi
      Properties:
        Path: /{proxy+}
        Method: OPTIONS
        Auth:
          Authorizer: NONE
        ApiId: !Ref MyHttpApi
Ashankz
  • 51
  • 2
3

With your script, CORS is enabled on API Gateway, but SAM always creates PROXY integration from the API Gateway to Lambda, which means that API Gateway cannot add any integration response and it will pass the response which it will receive from LAMBDA. That's why you need to handle the CORS header inside your lambda rather than depending on API Gateway. Below is the sample code to return from the LAMBDA along with the response.

return {
        statusCode: 200,
        headers: {
            "Access-Control-Allow-Headers" : "Content-Type,X-Amz-Date,Authorization,X-Api-Key,x-requested-with",
            "Access-Control-Allow-Origin": "*", // Allow from anywhere 
            "Access-Control-Allow-Methods": "OPTIONS,POST,GET,PUT,DELETE,PATCH" // Allow only GET request 
        },
        body: JSON.stringify(response)
    }
Nitesh Malviya
  • 794
  • 1
  • 9
  • 17
3

Late to the party, but for anyone else from Google: add this to the Auth section, this won't allow Authorizer to process CORS HTTP headers

Api:
  Auth:
    AddDefaultAuthorizerToCorsPreflight: false
  • 2
    "this won't allow Authorizer to process CORS HTTP headers" - I believe that is incorrect. What I believe this option does is removes authorization for HTTP OPTIONS requests (requests, not headers); CORS [preflight requests](https://developer.mozilla.org/en-US/docs/Glossary/Preflight_request) are OPTIONS requests. The CORS protocol dictates that these should be answered without the need for authentication, hence the need to turn it off with `AddDefaultAuthorizerToCorsPreflight`, ["CORS-preflight requests must never include credentials."](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) – Colm Bhandal Jul 08 '22 at 14:22
0

To Enable Cors on API gateway and CloudFormation/SAM work, we need to do a few things:

  1. Add the OPTIONS method to your endpoint to provide a preflight handshake response (official docs).
  2. Define the required headers placeholders in the response section ( Access-Control-Allow-Origin ,etc)
  3. Assign values to those headers in the integration section ('*' ,etc )
  4. Update your response template to pass those headers in the response to the client

Here is a sample file that shows how it is being defined:

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

Resources:
  SignupApi:
    Type: 'AWS::Serverless::Api'
    Properties:
      StageName: prod
      Cors:
        AllowMethods: "'POST'"
        AllowHeaders: "'Content-Type'"
        AllowOrigin: "'*'"
      DefinitionBody:
        swagger: '2.0'
        info:
          title: 'Signup API'
          version: '1.0.0'
        paths:
          /signup:
            options:
              summary: CORS support
              description: |
                Enable CORS by returning correct headers
              consumes:
                - application/json
              produces:
                - application/json
              tags:
                - CORS
              x-amazon-apigateway-integration:
                type: mock
                requestTemplates:
                  application/json: |
                    {
                      "statusCode" : 200
                    }
                responses:
                  "default":
                    statusCode: "200"
                    responseParameters:
                      method.response.header.Access-Control-Allow-Headers: "'Content-Type,X-Amz-Date,Authorization,X-Api-Key'"
                      method.response.header.Access-Control-Allow-Methods: "'*'"
                      method.response.header.Access-Control-Allow-Origin: "'*'"
                    responseTemplates:
                      application/json: |
                        {}
              responses:
                '200':
                  description: Default response for CORS method
                  headers:
                    Access-Control-Allow-Headers:
                      type: "string"
                    Access-Control-Allow-Methods:
                      type: "string"
                    Access-Control-Allow-Origin:
                      type: "string"
            post:
              produces:
                - application/json
              responses:
                '200':
                  description: Default response for CORS method
                  headers:
                    Access-Control-Allow-Headers:
                      type: "string"
                    Access-Control-Allow-Methods:
                      type: "string"
                    Access-Control-Allow-Origin:
                      type: "string"
              parameters:
                - name: body
                  in: body
                  required: true
                  schema:
                    $ref: '#/definitions/SignupRequest'
              x-amazon-apigateway-integration:
                uri: !Sub 'arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${SignupFunction.Arn}/invocations'
                passthroughBehavior: 'when_no_match'
                httpMethod: 'POST'
                type: 'aws'
                integrationHttpMethod: 'POST'
                requestTemplates:
                  application/json:  |
                    #set($allHeaders = $input.params().header)
                        {
                          #foreach($header in $allHeaders.keySet())
                            "$header": "$util.escapeJavaScript($allHeaders.get($header))" #if($foreach.hasNext),#end
                          #end
                          "body": $input.json("$.body")
                        }
                responses:
                  default:
                    statusCode: '200'
                    responseParameters:
                      method.response.header.Access-Control-Allow-Headers: "'Content-Type,X-Amz-Date,Authorization,X-Api-Key'"
                      method.response.header.Access-Control-Allow-Methods: "'*'"
                      method.response.header.Access-Control-Allow-Origin: "'*'"
                    responseTemplates:
                      application/json: '$input.json("$.body")'
        definitions:
          SignupRequest:
            type: 'object'
            properties:
              name:
                type: 'string'
              email:
                type: 'string'
              password:
                type: 'string'
  SignupFunction:
    Type: 'AWS::Serverless::Function'
    Properties:
      Handler: index.lambda_handler
      Runtime: python3.8
      InlineCode: |
        import json

        def lambda_handler(event, context):
            response = {
                'statusCode': 200,
                'body': json.dumps({'message': 'Signup successful'})
            }
            return response
      Events:
        SignUp:
          Type: Api
          Properties:
            Path: /signup
            Method: post
            RestApiId: !Ref SignupApi
Mahmoud
  • 698
  • 1
  • 8
  • 9