The Adventure of Access-Control-Allow-Credentials

I followed this guide to build an API using Lambda and API Gateway and encountered some problems along the way, but finally managed to fix the problems (thanks to Firefox’s network error logs).

Maybe the guide is already covering this and I simply missed it when I was reading it. I wanted to share my journey here in case others might find it helpful.


I created a Lambda function, an Auth0 Custom Authorizer function, and put it behind API Gateway to expose the Lambda function to the HTTP world and securing it with the Custom Authorizer at the same time.

Everything seemed to be working just fine. I uploaded an HTML page to an S3 static website and I could login via Auth0 with no problem.

I could also obtain a JWT token:

curl --request POST \
  --url https://xyz.auth0.com/oauth/token \
  --header 'content-type: application/json' \
  --data '{"client_id":"...","client_secret":"...","audience":"...","grant_type":"client_credentials"}'

and then go to the API Gateway console to test the custom authorizer and it was working like a charm:

However, when I later added some logic to the HTML page to actually invoke the API using jQuery, the OPTION request succeeded but then I got this error message in Chrome:

Failed to load https://xyz.execute-api.xyz.amazonaws.com/xyz: Request header field Access-Control-Allow-Credentials is not allowed by Access-Control-Allow-Headers in preflight response.

Being a total CORS noob, this was not very helpful to me. I opened the website in Firefox and after reading the error message a couple of times, I I finally understood what it means:

Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at https://xyz.execute-api.xyz.amazonaws.com/xyz. (Reason: missing token ‘access-control-allow-credentials’ in CORS header ‘Access-Control-Allow-Headers’ from CORS preflight channel)

So what did it mean?

By default, when enabling CORS on API Gateway, it sets the value of the Access-Control-Allow-Headers to Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token. As you can see, access-control-allow-credentials is missing there.

I edited the value of the header on the OPTION method and changed it to Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token,Access-Control-Allow-Credentials and voila! The error message vanished and the requests succeeded.

Just for the sake of completeness, the following code snippet show how I was trying to invoke my API:

var settings = {
  "async": true,
  "crossDomain": true,
  "url": "https://xyz.auth0.com/oauth/token",
  "method": "POST",
  "headers": {
    "content-type": "application/json"
  },
  "data": "{\"client_id\":\"...\",\"client_secret\":\"...\",\"audience\":\"...\",\"grant_type\":\"client_credentials\"}"
}

$.ajax(settings).done(function (response) {
  var payload = {
      source: "lorem ipsum"
  };

  $.ajax({
      type: "POST",
      url: "https://my-api.execute-api.xyz.amazonaws.com/xyz";,
      data: JSON.stringify(payload),
      headers: {
        "Authorization": `Bearer ${response.access_token}`
      },
      success: function (response) {
          console.log(response);
      }
  });
});
2 Likes

Hi all,

There’s one critical security issue in the code above: you don’t need to/must not expose your client_secret like that.

If you configure your WebAuth instance like this:

var webAuth = new auth0.WebAuth({
  domain: AUTH0_DOMAIN,
  clientID: AUTH0_CLIENT_ID,
  redirectUri: AUTH0_CALLBACK_URL,
  audience: '<unique audience ID for your API>',
  responseType: 'code token id_token',
  scope: 'openid profile email',
  leeway: 60
});

then Auth0 will store the access_token as a JWT in your browser’s local storage. Then you can issue AJAX requests like this:

var payload = {
    source: "lorem ipsum"
};

$.ajax({
    type: "POST",
    url: "<your API endpoint>",
    data: JSON.stringify(payload),
    headers: {
      "Authorization": `Bearer ${localStorage.getItem('access_token')}`
    },
    success: function (response) {
        console.log(response);
    }
});

The important part is:

audience: '<unique audience ID for your API>'

For more information see here.

Hey there!

Sorry for such huge delay in response! We’re doing our best in providing you with best developer support experience out there, but sometimes our bandwidth is not enough comparing to the number of incoming questions.

Wanted to reach out to know if you still require further assistance?