4

I have an OpenAPI spec for an api which I am deploying via CDK. The spec looks like:

openapi: 3.0.1
info:
  title: My API
  description: My REST API with CORS enabled
  version: 0.1.0

x-amazon-apigateway-cors:
  allowOrigins:
    - "*"
  allowCredentials: true
  exposeHeaders:
    - "x-apigateway-header"
    - "x-amz-date"
    - "content-type"
  maxAge: 3600
  allowMethods:
    - "*"
  allowHeaders":
    - "x-apigateway-header"
    - "x-amz-date"
    - "content-type"
    - "Authorization"

components:
  securitySchemes:
    lambda:
      type: "apiKey"
      name: "Authorization"
      in: "header"
      x-amazon-apigateway-authtype: "custom"
      x-amazon-apigateway-authorizer:
        authorizerUri: "{{my-lambda-authorizer}}"
        authorizerResultTtlInSeconds: 300
        type: "token" 

paths:
  /user/{id}:
    get:
      summary: Get info of specified user.
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/User'
      security:
        - lambda: []
      x-amazon-apigateway-integration:
        uri: "{{my-lambda}}"
        passthroughBehavior: "when_no_match"
        httpMethod: "POST"
        type: "aws_proxy"

When I try to access this via fetch(), I get an error Failed to load resource: Origin http://localhost:8000 is not allowed by Access-Control-Allow-Origin.

fetch('https://api.example.com/user/1')
        .then(response => response.json())
        .then((user: User) => {
            // do something
        })
        .catch((err) => {
            console.log("Error: " + err);
        });

The API is accessible at api.example.com and I am running the website locally using Gatsby at localhost:8000.

The AWS docs seem to state that CORS is enabled when I put x-amazon-apigateway-cors at the root of the spec, but CORS doesn't seem to be enabled (or working) when I try to access the API. How do I enable CORS for my API without needing to configure it in the console?

John
  • 10,837
  • 17
  • 78
  • 141

1 Answers1

10

Amazon API Gateway offers two types of APIs: REST APIs and HTTP APIs. REST APIs were the kind of APIs originally introduced with Amazon API Gateway, while HTTP APIs got announced at the end of 2019.

Based on the description of your OpenAPI specification I assume you're trying to deploy a REST API. The x-amazon-apigateway-cors OpenAPI extension however only works for HTTP APIs.

You have two choices now: You either switch to use a HTTP API or you configure CORS manually. As long as you don't need features only supported by REST APIs, I suggest you switch to use a HTTP API, as that's the more modern kind of API Amazon API Gateway offers.

If you want to use a REST API, enabling CORS requires more manual configuration. That's documented by AWS in Enabling CORS for a REST API resource. Essentially you have to ensure your integration returns proper CORS headers. For a NodeJS AWS Lambda function that could look like:

exports.handler = async (event) => {
    const response = {
        statusCode: 200,
        headers: {
            "Access-Control-Allow-Headers" : "Content-Type",
            "Access-Control-Allow-Origin": "https://www.example.com",
            "Access-Control-Allow-Methods": "OPTIONS,POST,GET"
        },
        body: JSON.stringify('Hello from Lambda!'),
    };
    return response;
};

For CORS pre-flight requests to work you'd also have to ensure that OPTIONS-requests also return the correct headers. The easiest way to achieve that is to add a mock-integration for the OPTIONS-request to your OpenAPI specification. That would look like:

options:
  responses:
    '200':
      description: Default response
      headers:
        Access-Control-Allow-Headers:
          schema:
            type: string
        Access-Control-Allow-Methods:
          schema:
            type: string
        Access-Control-Allow-Origin:
          schema:
            type: string
  x-amazon-apigateway-integration:
    type: mock
    requestTemplates:
      application/json: |
        {"statusCode" : 200}
    responses:
      default:
        statusCode: 200
        responseParameters:
          method.response.header.Access-Control-Allow-Headers: "'*'"
          method.response.header.Access-Control-Allow-Methods: "'OPTIONS,POST'"
          method.response.header.Access-Control-Allow-Origin: "'https://example.com/'"

Please also mind that modern browsers don't support localhost as origin for CORS, so you might need to work around that as well.

Dunedan
  • 7,848
  • 6
  • 42
  • 52
  • Thanks you, that's a remarkably helpful answer. You're right, it's a REST API as (a) it's the only one that supports OpenAPI via CDK and (b) I need to do authorization by inspecting a token as it doesn't look like a Cognito authorizer is rich enough for what I want. I've managed to get the CORS to work by returning the headers from within the proxy itself as per your answer, but only when I disable the custom lambda authorizer. Is there something more I need to do to be able to use a lambda authorizer here? – John Jul 04 '20 at 19:45
  • Does your client request successfully pass the authorizer? Because if not the authorizer will generate the responses, which won't contain the necessary CORS-headers. – Dunedan Jul 04 '20 at 19:57
  • Is there a way of having the authorizer return the headers? The docs suggest throwing an exception to indicate a 401, so I'm guessing that would cause the headers not to be passed. – John Jul 04 '20 at 20:01
  • 1
    Turns out there is another question on StackOverflow for that: https://stackoverflow.com/questions/36913196/401-return-from-an-api-gateway-custom-authorizer-is-missing-access-control-allo :-) – Dunedan Jul 04 '20 at 20:08
  • Thank you very much – John Jul 04 '20 at 20:12
  • I have followed these same steps from elsewhere, and am stuck at '403': 'It does not have HTTP ok status.' I have veried all response headers including HTTP 200/OK in curl, and am simply confused at this point! – Jamie Nicholl-Shelley Jul 24 '23 at 08:30