8

I'm trying to upload to GCP from the frontend with fetch, using a signed URL and I'm running into persistent CORS issue.

Is the file to be uploaded supposed to be embedded in the signedurl, or sent to the signedurl in a request body?

This is the error:

Access to fetch at <signedurl> from origin 'http://my.domain.com:3000' 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.

This is the CORS config on the bucket:

[
    {
      "origin": ["http://gcs.wuddit.com:3000"],
      "responseHeader": ["Content-Type", "Authorization",  "Content-Length", "User-Agent", "x-goog-resumable", "Access-Control-Allow-Origin"],
      "method": ["GET", "POST", "PUT", "DELETE"],
      "maxAgeSeconds": 3600
    }
]

This is the fetch call:

const uploadHandler = async (theFile, signedUrl) => {
  try {
    const response = await fetch(signedUrl, {
      method: 'POST',
      headers: {
        'Content-Type': theFile.type,
      },
      body: theFile,
    });
    const data = await response;
  } catch (error) {
    console.error('Upload Error:', error);
  }
};

Signed URL example:

// https://storage.googleapis.com/my-bucket-name/my-filename.jpg?X-Goog-Algorithm=GOOG4-RSA-SHA256&X-Goog-Credential=wuddit-images-service%40wuddit-427.iam.gserviceaccount.com%2F20210305%2Fauto%2Fstorage%2Fgoog4_request&X-Goog-Date=20210305T032415Z&X-Goog-Expires=901&X-Goog-SignedHeaders=content-type%3Bhost&X-Goog-Signature=18a2428f051e59fbeba0a8b97a824bdee0c70cffe1f9cce5696e95b9fd81b74974f1c291e5195e874eac29ede5619c9cf07538f21b442fb81e7fc1e764bb5444f5d7acae78e3f2b5876450fccde122f059348efc6d5d8be1bbef7250a1aa2433957d85e65f51c69e8daf020341cbf8044ed2b532205a331acc3728437c9295b25bb6e61ef71a99798bb38a6f05e664678d5e12aed916ab41d2e2f9e7e0974588b797ebef87f2c0949f7071687d1d12f232e871d892f6cd2da397888285783d5372657822275f56a44f9ca14a21fb4e4d6552d380f9e4a597d12663c51aea0a2bdc0f47994f687b59c9d629c1010245cefc975718f3574cd6ae331aa1b89d797d
Kirk Ross
  • 6,413
  • 13
  • 61
  • 104
  • it has to be send in the body (POST request), as an array of bytes ideally. You just have to allow CORS. Try to read the doc if it helps? https://cloud.google.com/storage/docs/cross-origin – Antonin GAVREL Mar 05 '21 at 00:02
  • had them same problem and none of the methods worked for me except this [react proxy] (https://create-react-app.dev/docs/proxying-api-requests-in-development/) – webcoder Mar 05 '21 at 00:15
  • @AntoninGAVREL I read the docs before posting here and did everything it said, so my issue must be something else. I'm sure it's some silly human error on my part. – Kirk Ross Mar 05 '21 at 02:09
  • 1
    One thing you probably want to check is the HTTP status code of the response. If it’s a 4xx or 5xx error, then you’ll know there’s some non-CORS cause, and the CORS error is just a side effect of that. You also should look at whatever server-side logs you have for the ` ` server — see what messages are getting logged there when it gets the CORS preflight OPTIONS request from the browser. – sideshowbarker Mar 05 '21 at 02:31
  • @sideshowbarker Thank you, but I'm not getting any response because I don't think it's making it to the server. I think it I need to match the headers in my fetch request exactly what's in the signedURL, but I'm not sure. As an aside, if I navigate to the signed URL directly in a browser I get an error. I added that to my OP, but I think it might be because I'm using the URL in a direct query vs. a fetch POST. – Kirk Ross Mar 05 '21 at 03:35
  • I think you need to configure the server to not require the signed-url checking for OPTIONS requests. That’s because the browser doesn’t send the Content-Type header in the CORS preflight OPTIONS request. The error cited in the question indicates the server is looking for the Content-Type header in the OPTIONS request. So it needs to be configured to not do that. And that’s something completely separate from your CORS config. (I personally know nothing about the signed-url stuff, so I can’t offer any guidance beyond that.) – sideshowbarker Mar 05 '21 at 05:15
  • @sideshowbarker Thank you kindly sir. When you say configure the server, do you mean my local Express backend that's generating the signed URL using the @google-cloud/storage module (configured with my service account creds), or do you mean Google's server / i.e. the bucket itself? – Kirk Ross Mar 05 '21 at 06:51
  • I mean Google's server — the bucket itself — assuming the *Signed URL error* XML message cited in the question is coming from there, and not from your local Express backend. But I can imagine it’s possible that my assumption is wrong, and that message is actually coming from your local Express backend. If so, then I don’t know even vaguely what to suggest about fixing that. (As I mentioned, I don’t know anything about the signed-url stuff, so I’m somewhat just speculating about where things might be failing here…) – sideshowbarker Mar 05 '21 at 08:51

2 Answers2

7

I figured this out. My lord. In my node Express backend, on the endpoint I was using to call the generateV4UploadSignedUrl() function, I had to setthe 'Access-Control-Allow-Origin' on theres`.

So this:

app.get('/api/gcloud', async (req, res) => {
  try {
    const url = await generateV4UploadSignedUrl().catch(console.error);
    res.json({ url });
  } catch (err) {
    console.log('err', err);
  }
});

Became this:

app.get('/api/gcloud', async (req, res) => {
  res.set('Access-Control-Allow-Origin', 'http://gcs.whatever.com:3000'); // magic line. Note this must match the domain on your GCP bucket config.
  try {
    const url = await generateV4UploadSignedUrl().catch(console.error);
    res.json({ url });
  } catch (err) {
    console.log('err', err);
  }
});

My bucket CORS config:

[
    {
      "origin": "http://gcs.whatever.com:3000",
      "responseHeader": ["Content-Type", "Authorization"],
      "method": ["GET", "POST", "PUT", "DELETE"],
      "maxAgeSeconds": 3600
    }
]

Save the above into a file, e.g. 'cors.json', then cd into the location where you saved the file, then use this gsutil command to set bucket CORS config:

gsutil cors set cors.json gs://your-bucket-name
Kirk Ross
  • 6,413
  • 13
  • 61
  • 104
  • 2
    1. Get the URL from your server is not related to GCP, it is just your server , [express-cors](https://expressjs.com/en/resources/middleware/cors.html) can help you. 2. When you upload files to GCP via signedUrl, you will have to whitelist your site to GCP – vanduc1102 Mar 01 '22 at 15:27
1

    [
            {
                "origin": ["*"],
                "method": ["*"],
                "maxAgeSeconds": 3600,
                "responseHeader": ["*"]
            }
        ]

worked for me. responseHeader was the missing ingredient!

Diego
  • 11
  • 1