How to enable CORS with AWS SAM
Asked Answered
B

6

17

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.

Buttock answered 12/4, 2021 at 20:16 Comment(6)
Can you provide your API response in the code?Cerelia
Are you testing api from browser or postman? Can you share response.Cosmo
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 cUrlButtock
Does this answer your question? Using CORS with AWS SAMCandescent
no this was actually more confusing for me because according to this comment https://mcmap.net/q/743268/-using-cors-with-aws-sam, the CORS config I made like i showed above should work, but i did not.Buttock
i now added the cors header in the response myself. i didnt find a better solutionButtock
C
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(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)
    }
Cosmo answered 14/4, 2021 at 12:14 Comment(2)
Link is present in lambda proxy with API GatewayCosmo
Worth pointing out that you ALSO need the Globals to be set in the SAM template.yml - as the answer from isaacsan doesPaine
S
21

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)
}
Scabious answered 3/11, 2021 at 11:30 Comment(6)
This is a correct solution. Also refer to this video from AWS - youtube.com/watch?v=wGDiOkqtUWADisquiet
Only one mistake. AllowCredentials cannot be true if you choose wildcard on AllowOriginLaux
Ah yup, good point. Gonna edit it now.Scabious
Three days looking for this gem. Thank you!Beaverboard
Hi @isaacsan123 what if I have multiple specific origins I would like to specify? Can I do something like: AllowOrigin: "'localhost:3000, localhost:3001'"?Ultracentrifuge
@Ultracentrifuge You can only specify 1 origin or all origins in the yaml file. Anything more dynamic would need something like a Lambda function to be called at a certain step to dynamically decide different permissions based on url. You can read about it here: aws.amazon.com/blogs/compute/….Scabious
C
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(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)
    }
Cosmo answered 14/4, 2021 at 12:14 Comment(2)
Link is present in lambda proxy with API GatewayCosmo
Worth pointing out that you ALSO need the Globals to be set in the SAM template.yml - as the answer from isaacsan doesPaine
H
6

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
Hedge answered 16/3, 2022 at 2:53 Comment(0)
S
5

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
Stpierre answered 21/9, 2021 at 19:52 Comment(1)
"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 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."Spellman
P
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)
    }
Presentational answered 11/9, 2021 at 11:39 Comment(0)
M
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
Melcher answered 20/3, 2023 at 2:9 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.