stevenpslade
stevenpslade

Reputation: 360

Google Cloud Storage request blocked by CORS: not allowed by Access-Control-Allow-Origin

What am I trying to accomplish?

I am trying to make a direct file upload to a Google Cloud Storage bucket. I'm successfully creating a direct upload link via my Ruby on Rails API ActiveStorage setup.

Using the created direct upload link, I am trying to make a PUT request to my Google Cloud Storage bucket in my frontend client (Next.js).

What issue am I encountering?

In my frontend client, I'm making a PUT request with fetch and receiving the following CORS error:

In Chrome:

Access to fetch at 'https://storage.googleapis.com//?GoogleAccessId=<ACCESS_ID>&Expires=1678985734&Signature=' from origin 'http://localhost:8080' 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.

In Safari:

Origin http://localhost:8080 is not allowed by Access-Control-Allow-Origin. Status code: 200 Failed to load resource: Origin http://localhost:8080 is not allowed by Access-Control-Allow-Origin. Status code: 200

Code Details

As per Google Cloud's CORS configuration, this is the CORS policy for my storage bucket:

[
  {
    "origin": ["http://localhost:8080"],
    "method": ["GET", "PUT"],
    "responseHeader": ["Origin", "Content-Type", "Content-MD5", "Content-Disposition"],
    "maxAgeSeconds": 3600
  }
]

I have confirmed this CORS configuration is present by running: gcloud storage buckets describe gs://<BUCKET_NAME> --format="default(cors)"

My direct upload fetch in my frontend client is pretty simple:

const directUpload = async (directUpload: DirectUpload) => {
  const response = await fetch(directUpload.url, {
    method: 'PUT',
    headers: JSON.parse(directUpload.headers),
    body: file,
  });

  return response;
}

The request headers for PUT (from the network tab) are as follows:

Content-Disposition: inline; filename="logo.png"; filename*=UTF-8''logo.png
Content-Type: image/png
Origin: http://localhost:8080
Referer: http://localhost:8080/
Accept: */*
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.6 Safari/605.1.15
Content-MD5: y+5qHSqBo9Kmlkln9P0vAQ==

The response headers from GCS from the PUT request (Status code 403):

alt-svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000,h3-Q050=":443"; ma=2592000,h3-Q046=":443"; ma=2592000,h3-Q043=":443"; ma=2592000,quic=":443"; ma=2592000; v="46,43"
content-length: 363
content-type: application/xml; charset=UTF-8
date: Mon, 20 Mar 2023 16:39:30 GMT
server: UploadServer
x-guploader-uploadid: <UPLOAD_ID>

The request headers of the preflight request:

:authority: storage.googleapis.com
:method: OPTIONS
:path: <DIRECT_UPLOAD_PATH>
:scheme: https
accept: */*
accept-encoding: gzip, deflate, br
accept-language: en-US,en;q=0.9
access-control-request-headers: content-disposition,content-md5,content-type
access-control-request-method: PUT
cache-control: no-cache
origin: http://localhost:8080
pragma: no-cache
referer: http://localhost:8080/
sec-fetch-dest: empty
sec-fetch-mode: cors
sec-fetch-site: cross-site
user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36

The response headers from GCS of the preflight request:

access-control-allow-headers: Content-Type,Content-MD5,Content-Disposition
access-control-allow-methods: GET,PUT
access-control-allow-origin: http://localhost:8080
access-control-max-age: 3600
alt-svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000,h3-Q050=":443"; ma=2592000,h3-Q046=":443"; ma=2592000,h3-Q043=":443"; ma=2592000,quic=":443"; ma=2592000; v="46,43"
cache-control: private, max-age=0
content-length: 0
content-type: text/html; charset=UTF-8
date: Thu, 16 Mar 2023 19:26:23 GMT
expires: Thu, 16 Mar 2023 19:26:23 GMT
server: UploadServer
vary: Origin
x-guploader-uploadid: <UPLOAD_ID>

I can see that the Origin header should match the origin setup in my bucket CORS policy.

Attempted Solutions

I have seen numerous other posts with similar CORS errors of not allowed by Access-Control-Allow-Origin. From these posts I have experimented with:

Upvotes: 5

Views: 8067

Answers (4)

vperezb
vperezb

Reputation: 413

I reached this post when trying to "GET" an static file from Storage when developing in localhost, what I've done to solve it's to update my bucket CORS policy as following:

You can find more related info in: https://cloud.google.com/storage/docs/cors-configurations#command-line

First create a file with this content my_local_setup_for_CORS.json:

  • Take into account the port you are using
  • The type of content you are planning to access
  • The methods you are planning to use for the requests
[
    {
        "origin": ["http://localhost:8080"],
        "responseHeader": ["Content-Type"],
        "method": ["GET"],
        "maxAgeSeconds": 3600
    }
]

After that use in the console the following command:

gsutil cors set my_local_setup_for_CORS.json gs://{name-of-your-bucket}

Remember to force a new reload "ctrl+f5" when trying it again.

Upvotes: 0

Asa S
Asa S

Reputation: 1

I resolved the issue by installing the cors node module and adding this on the requested server

const cors = require("cors");
app.use(cors());

Upvotes: -5

stevenpslade
stevenpslade

Reputation: 360

I was able to successfully make the PUT direct upload request to Google Cloud Storage after making a change found in this write up: https://finnian.io/blog/uploading-files-to-s3-react-native-ruby-on-rails

Finally I was able to make it work by forcing Active Storage to use v4 of the GCS presigned post URL. You can achieve this by setting the cache_control property of your service in storage.yml. I’m not sure why this setup doesn’t seem to work with v2, but there you go. Maybe it’ll help you.

This involved adding cache_control: "public, max-age=3600" to my google service in storage.yml as per active storage guide here: https://edgeguides.rubyonrails.org/active_storage_overview.html#google-cloud-storage-service

I also added "Cache-Control" to the responseHeader list uploaded to my GCS bucket CORS policy.

This resulted in the successful PUT request to GCS which uploaded the file to my bucket.

Adding cache_control in Rails forces using v4 (instead of v2) for generating signed URLs. More information on the signing URLs can be found here: https://cloud.google.com/storage/docs/access-control/signed-urls#types

• V4 signing with service account authentication: This signing mechanism is described below.

• V2 signing with service account authentication: This is a legacy mechanism for creating signed URLs, and its usage is not recommended.

Upvotes: 1

Summer Ibrahim
Summer Ibrahim

Reputation: 11

Clear your browser cache and cookies:

Browser caches can cause issues when changes are made to CORS policies. Clear your browser cache and cookies to ensure that you're working with the most recent CORS configuration.

Upvotes: 1

Related Questions