13

I was testing out the new API Gateway to secure a cloud function for my React application. So far the process has been much nicer than the previous alternatives, but I am currently getting CORS errors when trying to reach out to my API Gateway from my React app. I am setting the CORS headers correctly in my Cloud Function, but I have no known way of doing the same on the API Gateway endpoint. I am using Postman to test a request to the gateway endpoint and everything is working great, so it is just when I request from my React app.

Error: "Access to fetch at 'https://my-gateway-a12bcd345e67f89g0h.uc.gateway.dev/hello?key=example' from origin 'https://example.netlify.app' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled."

Would love some insight into this issue. Thanks!

ElektrikSpark
  • 613
  • 1
  • 8
  • 21
  • SOLUTION: The correct answer is the one answered by @user14982714. I will add an answer with more clarification below. After countless hours of debugging, I found out that Google API Gateway uses Endpoints under the hood. This means if you add the x-google-endpoints attribute with allowCors, the handling will be passed off to your server in which you can hendle it manually. – Anthony Sette May 24 '21 at 20:10
  • I just added a solution with more details. If anyone stumbles across this in the future be sure to check it out. Adding options as a path in your openapi.yaml and calling a function that returns the proper headers is not a viable solution for a complex API. @ElektrikSpark please check it out, and try it. – Anthony Sette May 24 '21 at 20:36

6 Answers6

19

This is not yet supported, however, there is a temporary workaround to get this working. You should add options to the paths in your openapi.yaml. Additionally, both get and options operations should point to the same cloud function, since the options request then acts as a warmup request for the cloud function. This is the most efficient setup, in terms of latency. Here is a simplified example:

paths:
  /helloworld:
    get:
      operationId: getHelloWorld
      x-google-backend:
        address: $CLOUD_FUNCTION_ADDRESS
      responses:
        '200':
          description: A successful response
    options:
      operationId: corsHelloWorld
      x-google-backend:
        address: $CLOUD_FUNCTION_ADDRESS
      responses:
        '200':
          description: A successful response

Then, in your cloud function backend, you must also handle the preflight request (source). The Google documentation also provides an example with authentication, which has some additional headers. Here is an example without authentication:

def cors_enabled_function(request):
    # For more information about CORS and CORS preflight requests, see
    # https://developer.mozilla.org/en-US/docs/Glossary/Preflight_request
    # for more information.

    # Set CORS headers for the preflight request
    if request.method == 'OPTIONS':
        # Allows GET requests from any origin with the Content-Type
        # header and caches preflight response for an 3600s
        headers = {
            'Access-Control-Allow-Origin': '*',
            'Access-Control-Allow-Methods': 'GET',
            'Access-Control-Allow-Headers': 'Content-Type',
            'Access-Control-Max-Age': '3600'
        }

        return ('', 204, headers)

    # Set CORS headers for the main request
    headers = {
        'Access-Control-Allow-Origin': '*'
    }

    return ('Hello World!', 200, headers)

Note: the downside of API gateway not managing preflight requests in a proper manner results in a penalty of running the cloud function twice. But your second request should always be very fast since the first request acts as a warmup request.

Cloudkollektiv
  • 11,852
  • 3
  • 44
  • 71
  • Hi can you share the full open api spec here, i added options at my open api spec but while uploading at GCP console it says invalid definition "options" options: operationId: corsRequest x-google-backend: address: >- https://us-east1-neodev-305805.cloudfunctions.net/my-function responses: '200': produces: - application/json schemes: - https swagger: '2.0' – SundarJavaDeveloper Apr 08 '21 at 07:11
  • I extended the openapi spec and gave some more clarification! Good luck :) – Cloudkollektiv Apr 08 '21 at 12:53
  • @SundarJavaDeveloper Is this resolved? I tried the same and now the request is not even sending options to server backend. how to configure options in yaml so that the GCP API Gateway sends OPTIONS first? – code tutorial May 06 '21 at 06:15
  • A CORS preflight request is a CORS request that checks to see if the CORS protocol is understood and a server is aware using specific methods and headers. It is an OPTIONS request, using three HTTP request headers: Access-Control-Request-Method , Access-Control-Request-Headers , and the Origin header. In the yaml snippet I posted there is an example options operation. – Cloudkollektiv May 06 '21 at 07:17
  • You saved my hours. The options at YAML file, resolves my cors issue for API Gateway custom domain endpoint issue – Nazmul Jun 15 '21 at 20:42
  • @Shuvro, very glad to hear! I hope they will include this in API gateway in the near future, so that the backend code becomes cleaner. – Cloudkollektiv Jun 16 '21 at 10:13
11

Solution

Here is the solution. It is just as user14982714 stated. Add a host and x-google-endpoints to your oepnapi.yaml file at the top level:

host: my-cool-api.endpoints.my-project-id.cloud.goog
x-google-endpoints:
- name: my-cool-api.endpoints.my-project-id.cloud.goog
  allowCors: True

However, be sure to replace my-cool-api.endpoints.my-project-id.cloud.goog with your API Managed Service URL. This can be found in your google cloud console under the API Gateway API here:

enter image description here

I covered the start to my endpoint name for privacy, however, yours should also end with .cloud.goog. If you haven't deployed a configuration yet, deploy it without the x-google-endpoints and host, then update it to include both. (To update your configuration go to API Gateway -> Your API -> Gateways Tab -> Your Gateway -> Edit- > Change API Config -> Create New)

Explanation

Now to explain why this works with Google Cloud API Gateway. The API Gateway uses endpoints under the hood. Most people don't know this is the case, however, after giving up on the API Gateway and moving back to Endpoints I noticed that my API Gateways were listed under the endpoint services. They do not show in the UI, but with the gcloud CLI, run this command gcloud endpoints services list and you should see your API Gateways. Crazy! But Google does this a lot.

enter image description here

So knowing this I tried adding allowCors: true to the x-google-endpoints and viola. It worked. I hope this helps someone out there.

Anthony Sette
  • 777
  • 1
  • 10
  • 26
  • 2
    You need cloud endpoints for this, which supports CORS. However, the original question is about using a cloud function as a backend. – Cloudkollektiv Jun 24 '21 at 08:57
  • 2
    This actually does work. If, like me, your API Gateway is behind a load balancer to support a custom domain name, you should use the original `cloud.goog` service name as shown in the command `gcloud endpoints services list` -- NOT the custom domain name. – chadkouse Oct 06 '21 at 16:27
  • Thanks for the elaboration, sorry if I misunderstood. I think in your specific case this is working with a load balancer. Could you add a detailed example of your openapi specification? – Cloudkollektiv Oct 14 '21 at 20:25
  • You can also use `gcloud api-gateway apis describe $GATEWAY_API_ID --project=$PROJECT --format='value(managedService)'` to get exactly what you need for specific gateway, practical if you are editing OpenApi specs file on deployment with shell scripts – Šime Apr 12 '22 at 13:20
  • I am using firebase auth to authorize the request against the backend. Following this approach, the gateway is rejecting the pref-light request beacause of the Authorization header. The options path approach is working for me. – sillo01 Sep 09 '22 at 19:49
10

Turns out that API Gateway does not currently have CORS support.

Reference.

yudhiesh
  • 6,383
  • 3
  • 16
  • 49
ElektrikSpark
  • 613
  • 1
  • 8
  • 21
3
swagger: "2.0"
host: "my-cool-api.endpoints.my-project-id.cloud.goog"
x-google-endpoints:
- name: "my-cool-api.endpoints.my-project-id.cloud.goog"
  allowCors: True

Note: host and name should have the same API endpoint name

Configuring these lines in your config file enables CORS for API GATEWAY

[Reference][1]

[1]: https://cloud.google.com/endpoints/docs/openapi/support-cors#:~:text=CORS%20(Cross%2Dorigin%20resource%20sharing,would%20prevent%20cross%2Dorigin%20requests.

Suraj Rao
  • 29,388
  • 11
  • 94
  • 103
2

I had the same issue and solve it with a load balancer (originally used to add a custom domain to my API gateway). I use my load balancer to add the missing header into the response.

You just need to add the "Access-Control-Origin" header:

Allow all

Access-Control-Origin:'*'

Allow a specific origin Access-Control-Allow-Origin: http://example.com:8080

You can find the instructions here GCP - Creating custom headers.

If you do not have a load balancer implemented, you can follow this tutorial to implement a new one Google API Gateway, Load Balancer and Content Delivery Network.

You can find more information regarding CORS at https://www.w3.org/wiki/CORS_Enabled.

Nycolas Silvestre
  • 1,445
  • 1
  • 10
  • 13
  • This did not work for me. Can you elaborate a bit more? Am I correct in assuming that you added an "Access-Control-Allow-Origin:*" header to your backend service and that this was all that was required? – Martin Reindl Apr 22 '22 at 04:57
1

I had similar issue with other API so I am not sure the same will work in your case but you can try - in react app when fetching the data, lets say with axios you can try

    axios.post('http://localhost:3003/signup',this.data,{headers:{'Access-Control- 
    Allow-Origin':'*','Content-Type': 'application/json'}})

on the backend side - try this -

  let cors=require('./cors')
  app.options('*', cors());

It works in my case , hope it will help you.

  • Thank you for the response Igor. Unfortunately it is still saying "No 'Access-Control-Allow-Origin' header is present on the requested resource.", so I believe this is strictly a server-side issue. – ElektrikSpark Oct 09 '20 at 14:32
  • You are thinking in the right direction, however, the GCP API Gateway layer the OP is talking about is blocking you from routing the request to the actual code. The solution lies in correctly specifying a path.options route in the openapi.yaml specification and routing this to a CORS specific backend. – Cloudkollektiv Oct 14 '21 at 20:22