[ajax] API Gateway CORS: no 'Access-Control-Allow-Origin' header

Although CORS has been set up through API Gateway and the Access-Control-Allow-Origin header is set, I still receive the following error when attempting to call the API from AJAX within Chrome:

XMLHttpRequest cannot load http://XXXXX.execute-api.us-west-2.amazonaws.com/beta/YYYYY. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'null' is therefore not allowed access. The response had HTTP status code 403.

I attempted to GET the URL through Postman and it shows the above header is successfully passed:

Passed headers

And from the OPTIONS reponse:

Response headers

How can I call my API from the browser without reverting to JSON-P?

This question is related to ajax amazon-web-services cors aws-api-gateway

The answer is


I am running aws-serverless-express, and in my case needed to edit simple-proxy-api.yaml.

Before CORS was configured to https://example.com, I just swapped in my site's name and redeployed via npm run setup, and it updated my existing lambda/stack.

#...
/:
#...
method.response.header.Access-Control-Allow-Origin: "'https://example.com'"
#...
/{proxy+}:
method.response.header.Access-Control-Allow-Origin: "'https://example.com'"
#...

In Python you can do it as in the code below:

{ "statusCode" : 200,
'headers': 
    {'Content-Type': 'application/json',
    'Access-Control-Allow-Origin': "*"
     },
"body": json.dumps(
    {
    "temperature" : tempArray,
    "time": timeArray
    })
 }

If you have tried everything regarding this issue to no avail, you'll end up where I did. It turns out, Amazon's existing CORS setup directions work just fine... just make sure you remember to redeploy! The CORS editing wizard, even with all its nice little green checkmarks, does not make live updates to your API. Perhaps obvious, but it stumped me for half a day.

enter image description here


I found a simple solution within

API Gateway > Select your API endpoint > Select the method (in my case it was the POST)

Now there is a dropdown ACTIONS > Enable CORS .. select it.

Now select the dropdown ACTIONS again > Deploy API (re-deploy it)

enter image description here

It worked !


For Googlers:

Here is why:

  • Simple request, or, GET/POST with no cookies do not trigger preflight
  • When you configure CORS for a path, API Gateway will only create an OPTIONS method for that path, then send Allow-Origin headers using mock responses when user calls OPTIONS, but GET / POST will not get Allow-Origin automatically
  • If you try to send simple requests with CORS mode on, you will get an error because that response has no Allow-Origin header
  • You may adhere to best practice, simple requests are not meant to send response to user, send authentication/cookie along with your requests to make it "not simple" and preflight will trigger
  • Still, you will have to send CORS headers by yourself for the request following OPTIONS

To sum it up:

  • Only harmless OPTIONS will be generated by API Gateway automatically
  • OPTIONS are only used by browser as a precautious measure to check possibility of CORS on a path
  • Whether CORS is accepted depend on the actual method e.g. GET / POST
  • You have to manually send appropriate headers in your response

Deploying the code after enabling CORS for both POST and OPTIONS worked for me.


For me, the answer that FINALLY WORKED, was the comment from James Shapiro from Alex R's answer (second most upvoted). I got into this API Gateway problem in the first place, by trying to get a static webpage hosted in S3 to use lambda to process the contact-us page and send an email. Simply checking [ ] Default 4XX fixed the error message.

enter image description here


If anyone else is running into this still - I was able to track down the root cause in my application.

If you are running API-Gateway with custom Authorizers - API-Gateway will send a 401 or 403 back before it actually hits your server. By default - API-Gateway is NOT configured for CORS when returning 4xx from a custom authorizer.

Also - if you happen to be getting a status code of 0 or 1 from a request running through API Gateway, this is probably your issue.

To fix - in the API Gateway configuration - go to "Gateway Responses", expand "Default 4XX" and add a CORS configuration header there. i.e.

Access-Control-Allow-Origin: '*'

Make sure to re-deploy your gateway - and voila!


Got my sample working: I just inserted 'Access-Control-Allow-Origin': '*', inside headers:{} in the generated nodejs Lambda function. I made no changes to the Lambda-generated API layer.

Here's my NodeJS:

'use strict';
const doc = require('dynamodb-doc');
const dynamo = new doc.DynamoDB();
exports.handler = ( event, context, callback ) => {
    const done = ( err, res ) => callback( null, {
        statusCode: err ? '400' : '200',
        body: err ? err.message : JSON.stringify(res),
        headers:{ 'Access-Control-Allow-Origin' : '*' },
    });
    switch( event.httpMethod ) {
        ...
    }
};

Here's my AJAX call

$.ajax({
    url: 'https://x.execute-api.x-x-x.amazonaws.com/prod/fnXx?TableName=x',
    type: 'GET',
    beforeSend: function(){ $( '#loader' ).show();},
    success: function( res ) { alert( JSON.stringify(res) ); },
    error:function(e){ alert('Lambda returned error\n\n' + e.responseText); },
    complete:function(){ $('#loader').hide(); }
});

In my case, I was simply writing the fetch request URL wrong. On serverless.yml, you set cors to true:

register-downloadable-client:
    handler: fetch-downloadable-client-data/register.register
    events:
      - http:
          path: register-downloadable-client
          method: post
          integration: lambda
          cors: true
          stage: ${self:custom.stage}

and then on the lambda handler you send the headers, but if you make the fetch request wrong on the frontend, you're not going to get that header on the response and you're going to get this error. So, double check your request URL on the front.


In addition to others comments, something to look out for is the status returned from your underlying integration and if the Access-Control-Allow-Origin header is returned for that status.

Doing the 'Enable CORS' thing only sets up 200 status. If you have others on the endpoint, e.g 4xx and 5xx, you need to add the header yourself.


For me, as I was using pretty standard React fetch calls, this could have been fixed using some of the AWS Console and Lambda fixes above, but my Lambda returned the right headers (I was also using Proxy mode) and I needed to package my application up into a SAM Template, so I could not spend my time clicking around the console.

I noticed that all of the CORS stuff worked fine UNTIL I put Cognito Auth onto my application. I just basically went very slow doing a SAM package / SAM deploy with more and more configurations until it broke and it broke as soon as I added Auth to my API Gateway. I spent a whole day clicking around wonderful discussions like this one, looking for an easy fix, but then ended up having to actually read about what CORS was doing. I'll save you the reading and give you another easy fix (at least for me).

Here is an example of an API Gateway template that finally worked (YAML):

Resources:
  MySearchApi:
    Type: AWS::Serverless::Api
    Properties:
      StageName: 'Dev'
      Cors:
        AllowMethods: "'OPTIONS, GET'"
        AllowHeaders: "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token'"
        AllowOrigin: "'*'"
      Auth:
        DefaultAuthorizer: MyCognitoSearchAuth
        Authorizers:
          MyCognitoSearchAuth:
            UserPoolArn: "<my hardcoded user pool ARN>"
            AuthType: "COGNITO_USER_POOLS"
        AddDefaultAuthorizerToCorsPreflight: False

Note the AddDefaultAuthorizerToCorsPreflight at the bottom. This defaults to True if you DON'T have it in your template, as as far as I can tell from my reading. And, when True, it sort of blocks the normal OPTIONS behavior to announce what the Resource supports in terms of Allowed Origins. Once I explicitly added it and set it to False, all of my issues were resolved.

The implication is that if you are having this issue and want to diagnose it more completely, you should visit your Resources in API Gateway and check to see if your OPTIONS method contains some form of Authentication. Your GET or POST needs Auth, but if your OPTIONS has Auth enabled on it, then you might find yourself in this situation. If you are clicking around the AWS console, then try removing from OPTIONS, re-deploy, then test. If you are using SAM CLI, then try my fix above.


I just added headers to my lambda function response and it worked like a charm

exports.handler = async (event) => {
    const response = {
        statusCode: 200,
        body: JSON.stringify('Hey it works'),
        headers:{ 'Access-Control-Allow-Origin' : '*' }
    };
    return response;
};

I get the same problem. I have used 10hrs to findout.

https://serverless.com/framework/docs/providers/aws/events/apigateway/

// handler.js

'use strict';

module.exports.hello = function(event, context, callback) {

const response = {
  statusCode: 200,
  headers: {
    "Access-Control-Allow-Origin" : "*", // Required for CORS support to work
    "Access-Control-Allow-Credentials" : true // Required for cookies, authorization headers with HTTPS 
  },
  body: JSON.stringify({ "message": "Hello World!" })
};

callback(null, response);
};

Another root cause of this problem might be a difference between HTTP/1.1 and HTTP/2.

Symptom: Some users, not all of them, reported to get a CORS error when using our Software.

Problem: The Access-Control-Allow-Origin header was missing sometimes.

Context: We had a Lambda in place, dedicated to handling OPTIONS request and replying with the corresponding CORS headers, such as Access-Control-Allow-Origin matching a whitelisted Origin.

Solution: The API Gateway seems to transform all headers to lower-case for HTTP/2 calls, but maintains capitalization for HTTP/1.1. This caused the access to event.headers.origin to fail.

Check if you're having this issue too:

Assuming your API is located at https://api.example.com, and your front-end is at https://www.example.com. Using CURL, make a request using HTTP/2:

curl -v -X OPTIONS -H 'Origin: https://www.example.com' https://api.example.com

The response output should include the header:

< Access-Control-Allow-Origin: https://www.example.com

Repeat the same step using HTTP/1.1 (or with a lowercase Origin header):

curl -v -X OPTIONS --http1.1 -H 'Origin: https://www.example.com' https://api.example.com

If the Access-Control-Allow-Origin header is missing, you might want to check case sensitivity when reading the Origin header.


After Change your Function or Code Follow these two steps.

First Enable CORS Then Deploy API every time.


I got mine working after I realised that the lambda authoriser was failing and for some unknown reason that was being translated into a CORS error. A simple fix to my authoriser (and some authoriser tests that I should have added in the first place) and it worked. For me the API Gateway action 'Enable CORS' was required. This added all the headers and other settings I needed in my API.


1) I needed to do the same as @riseres and some other changes.This are my response headers:

headers: {
            'Access-Control-Allow-Origin' : '*',
            'Access-Control-Allow-Headers':'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token',
            'Access-Control-Allow-Credentials' : true,
            'Content-Type': 'application/json'
        }

2) And

According to this documentation:

http://docs.aws.amazon.com/apigateway/latest/developerguide/how-to-cors.html

When you use proxy for lambda functions on API Gateway config, the post or get methods have no added headers, only the options does. You must do it manually in the response(server or lambda response).

3) And

Beside that, I needed to disable the 'API Key Required' option in my API gateway post method.


In my case, since I was using AWS_IAM as the Authorization method for API Gateway, I needed to grant my IAM role permissions to hit the endpoint.


Examples related to ajax

Getting all files in directory with ajax Cross-Origin Read Blocking (CORB) Jquery AJAX: No 'Access-Control-Allow-Origin' header is present on the requested resource Fetch API request timeout? How do I post form data with fetch api? Ajax LARAVEL 419 POST error Laravel 5.5 ajax call 419 (unknown status) How to allow CORS in react.js? Angular 2: How to access an HTTP response body? How to post a file from a form with Axios

Examples related to amazon-web-services

How to specify credentials when connecting to boto3 S3? Is there a way to list all resources in AWS Access denied; you need (at least one of) the SUPER privilege(s) for this operation Job for mysqld.service failed See "systemctl status mysqld.service" What is difference between Lightsail and EC2? AWS S3 CLI - Could not connect to the endpoint URL boto3 client NoRegionError: You must specify a region error only sometimes How to write a file or data to an S3 object using boto3 Missing Authentication Token while accessing API Gateway? The AWS Access Key Id does not exist in our records

Examples related to cors

Axios having CORS issue Cross-Origin Read Blocking (CORB) Jquery AJAX: No 'Access-Control-Allow-Origin' header is present on the requested resource How to allow CORS in react.js? Set cookies for cross origin requests XMLHttpRequest blocked by CORS Policy How to enable CORS in ASP.net Core WebAPI No 'Access-Control-Allow-Origin' header is present on the requested resource—when trying to get data from a REST API How to overcome the CORS issue in ReactJS Trying to use fetch and pass in mode: no-cors

Examples related to aws-api-gateway

Getting json body in aws Lambda via API gateway Missing Authentication Token while accessing API Gateway? API Gateway CORS: no 'Access-Control-Allow-Origin' header Can an AWS Lambda function call another How to pass a querystring or route parameter to AWS Lambda from Amazon API Gateway