Jan
Jan

Reputation: 563

How to enable CORS with AWS SAM

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.

Upvotes: 18

Views: 20565

Answers (6)

Vadim Rastyagaev
Vadim Rastyagaev

Reputation: 51

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

Upvotes: 5

nirvana124
nirvana124

Reputation: 1033

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(GET/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)
    }

Upvotes: 9

Mahmoud
Mahmoud

Reputation: 1038

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

Upvotes: 0

isaacsan 123
isaacsan 123

Reputation: 1158

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)
}

Upvotes: 23

Ashankz
Ashankz

Reputation: 101

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

Upvotes: 6

Nitesh Malviya
Nitesh Malviya

Reputation: 840

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)
    }

Upvotes: 3

Related Questions