skip to Main Content

I’m using terraform with AWS provider to create an API Gateway. Everything seems to be fine except for the CORS settings. I’m also using OpenAPI for the configuration.

My "end user" problem is that my API call to my back end is returning 404 not found because it’s making an OPTIONS request first. Enabling CORS settings in API Gateway handles CORS for you and also automatically returns the correct response for the OPTIONS request. However, CORS settings are not being enabled. I believe they should be due to the x-amazon-apigateway-cors section.

Here is my OpenAPI file:

openapi: 3.0.1

info:
  title: App API
  description: App API
  version: 0.1.0

paths:
  '/requisitions':
    post:
      operationId: createRequisition
      summary: Create requisition
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - institutionId
              properties:
                institutionId:
                  type: string
      responses:
        '200':
          description: 200 response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Requisition'
        '401':
          $ref: '#/components/responses/Unauthenticated'
        default:
          $ref: '#/components/responses/Error'
      x-amazon-apigateway-integration:
        type: AWS_PROXY
        httpMethod: POST
        uri: '${gocardless_function_arn}'
        payloadFormatVersion: 2.0

security:
  - cognito-jwt: []

x-amazon-apigateway-cors:
  allowOrigins:
    - '*'
  allowMethods:
    - GET
    - OPTIONS
    - POST
  allowHeaders:
    - x-amzm-header
    - x-apigateway-header
    - x-api-key
    - authorization
    - x-amz-date
    - content-type

components:
  schemas:
    AnyValue:
      nullable: true
      description: Can be any value - null, string, number, boolean, array or object.
    Requisition:
      type: object
      properties:
        id:
          type: string
          nullable: true
    Error:
      type: object
      required:
        - status
        - statusCode
        - error
      properties:
        status:
          type: string
        statusCode:
          type: integer
        requestId:
          type: string
        documentationUrl:
          type: string
        error:
          type: object
          required:
            - code
            - message
            - timestamp
          properties:
            code:
              type: string
            message:
              type: string
            details:
              $ref: '#/components/schemas/AnyValue'
            timestamp:
              type: string
            path:
              type: string
            suggestion:
              type: string

  securitySchemes:
    cognito-jwt:
      type: oauth2
      flows:
        authorizationCode:
          authorizationUrl: 'https://cognito-idp.eu-west-2.amazonaws.com/'
          tokenUrl: ''
          scopes: {}

      x-amazon-apigateway-authorizer:
        type: jwt
        jwtConfiguration:
          issuer: 'https://cognito-idp.eu-west-2.amazonaws.com/${cognito_user_pool_id}'
          audience:
            - '${cognito_app_client_id}'
        identitySource: '$request.header.Authorization'

  responses:
    Unauthenticated:
      description: Unauthenticated
      headers:
        www-authenticate:
          schema:
            type: string
      content:
        application/json:
          schema:
            type: object
            required:
              - message
            properties:
              message:
                type: string
    Error:
      description: Error
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'

And here is the relevant part of my terraform:

resource "aws_apigatewayv2_api" "api_gateway" {
  name          = "API Gateway"
  version       = "0.1.0"
  protocol_type = "HTTP"
  description   = "API gateway"
  body = templatefile("${path.module}/files/api-gateway-openapi.yaml", {
    gocardless_function_arn = module.gocardless_create_requisition_lambda.lambda_function_arn,
    cognito_user_pool_id    = aws_cognito_user_pool.pool.id,
    cognito_app_client_id   = aws_cognito_user_pool_client.client.id,
    region                  = var.region
  })
}

resource "aws_apigatewayv2_deployment" "api_gateway_deployment" {
  api_id      = aws_apigatewayv2_api.api_gateway.id
  description = "API Gateway deployment"

  triggers = {
    redeployment = sha1(join(
      ",", tolist([
        templatefile("${path.module}/files/api-gateway-openapi.yaml", {
          gocardless_function_arn = module.gocardless_create_requisition_lambda.lambda_function_arn,
          cognito_user_pool_id    = aws_cognito_user_pool.pool.id,
          cognito_app_client_id   = aws_cognito_user_pool_client.client.id,
          region                  = var.region
        }),
        jsonencode(aws_apigatewayv2_stage.api_gateway_default_stage)
      ])
    ))
  }
}

resource "aws_apigatewayv2_stage" "api_gateway_default_stage" {
  api_id      = aws_apigatewayv2_api.api_gateway.id
  name        = "default"
  auto_deploy = true
  default_route_settings {
    throttling_rate_limit  = 10
    throttling_burst_limit = 10
  }
}

I tried enabling CORS manually through "ClickOps" in the AWS console. The request from my front end immediately started working (OPTIONS, followed by POST).

I’m expecting the x-amazon-apigateway-cors (https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-swagger-extensions-cors-configuration.html) section to already be enabling these CORS options without me having to do them manually. By doing them manually, they also get overriden every time I re-run terraform apply.

What should I be change in the OpenAPI file to enable the CORS configuration in API GW?

2

Answers


  1. Chosen as BEST ANSWER

    After looking around for a few more hours, I luckily stumbled across a method that works.

    Unless I'm doing something very wrong, the x-amazon-apigateway-cors extension wasn't working.

    What worked was adding additional options to the terraform code. My terraform code now looks like this, with the property "cors_configuration" added:

    resource "aws_apigatewayv2_api" "api_gateway" {
      name          = "API Gateway"
      version       = "0.1.0"
      protocol_type = "HTTP"
      description   = "API gateway"
      body = templatefile("${path.module}/files/api-gateway-openapi.yaml", {
        gocardless_function_arn = module.gocardless_create_requisition_lambda.lambda_function_arn,
        cognito_user_pool_id    = aws_cognito_user_pool.pool.id,
        cognito_app_client_id   = aws_cognito_user_pool_client.client.id,
        region                  = var.region
      })
      cors_configuration {
        allow_origins = ["*"]
        allow_headers = ["*"]
        allow_methods = ["*"]
        max_age       = 3600
      }
    }
    

    That applies the CORS configuration correctly on the API GW and automatically makes the OPTIONS requests work.


  2. The idea you need to add MOCK integration for OPTIONS. I have written a module for applying CORS to API Gateway and you can use it and attach to your API Gateway resource. main.tf will be.

      ####CORS####
    resource "aws_api_gateway_resource" "cors" {
      rest_api_id = var.api_id
      parent_id   = var.api_resource_id
      path_part   = "{cors+}"
    }
    
    resource "aws_api_gateway_method" "cors" {
      rest_api_id   = var.api_id
      resource_id   = aws_api_gateway_resource.cors.id
      http_method   = "OPTIONS"
      authorization = "NONE"
    }
    
    resource "aws_api_gateway_integration" "cors" {
      rest_api_id = var.api_id
      resource_id = aws_api_gateway_resource.cors.id
      http_method = aws_api_gateway_method.cors.http_method
      type = "MOCK"
          request_templates = {
        "application/json" = jsonencode(
          {
            statusCode = 200
          }
        )
      }
    }
    
    resource "aws_api_gateway_method_response" "cors" {
      depends_on = [aws_api_gateway_method.cors]
      rest_api_id = var.api_id
      resource_id = aws_api_gateway_resource.cors.id
      http_method = aws_api_gateway_method.cors.http_method
      status_code = 200
      response_parameters = {
        "method.response.header.Access-Control-Allow-Origin" = true,
        "method.response.header.Access-Control-Allow-Methods" = true,
        "method.response.header.Access-Control-Allow-Headers" = true
      }
      response_models = {
        "application/json" = "Empty"
      }
    }
    
    resource "aws_api_gateway_integration_response" "cors" {
      depends_on = [aws_api_gateway_integration.cors, aws_api_gateway_method_response.cors]
      rest_api_id = var.api_id
      resource_id = aws_api_gateway_resource.cors.id
      http_method = aws_api_gateway_method.cors.http_method
      status_code = 200
      response_parameters = {
        "method.response.header.Access-Control-Allow-Origin" = "'*'", # replace with hostname of frontend (CloudFront)
        "method.response.header.Access-Control-Allow-Headers" = "'Content-Type'",
        "method.response.header.Access-Control-Allow-Methods" = "'GET, POST, DELETE'" # remove or add HTTP methods as needed
      }
    }
    
    ############
    

    and Variables.tf file will be

    # -----------------------------------------------------------------------------
    # Variables: API Gateway
    # -----------------------------------------------------------------------------
    
    # var.api_id
    variable "api_id" {
      description = "API identifier"
    }
    
    # var.api_resource_id
    variable "api_resource_id" {
      description = "API resource identifier"
    }
    
    # -----------------------------------------------------------------------------
    # Variables: CORS-related
    # -----------------------------------------------------------------------------
    
    # var.allow_headers
    variable "allow_headers" {
      description = "Allow headers"
      type        = list(string)
    
      default = [
        "Authorization",
        "Content-Type",
        "X-Amz-Date",
        "X-Amz-Security-Token",
        "X-Api-Key",
      ]
    }
    
    # var.allow_methods
    variable "allow_methods" {
      description = "Allow methods"
      type        = list(string)
    
      default = [
        "OPTIONS",
        "HEAD",
        "GET",
        "POST",
        "PUT",
        "PATCH",
        "DELETE",
      ]
    }
    
    # var.allow_origin
    variable "allow_origin" {
      description = "Allow origin"
      type        = string
      default     = "*"
    }
    
    # var.allow_max_age
    variable "allow_max_age" {
      description = "Allow response caching time"
      type        = string
      default     = "7200"
    }
    
    # var.allowed_credentials
    variable "allow_credentials" {
      description = "Allow credentials"
      default     = false
    }
    

    output.tf

    output "cors_resource_id" {
      value       =  aws_api_gateway_resource.cors.id
    } 
    

    then you can use this module

    resource "aws_api_gateway_rest_api" "api" {
      name = var.api_name
      tags = var.tags
    }
    
    ####CORS####
    
    module "cors" {
      source = "../api-gateway-cors"
    
      api_id          = aws_api_gateway_rest_api.api.id
      api_resource_id = aws_api_gateway_rest_api.api.root_resource_id
    }
    
    ############
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search