Debugging Google Cloud Storage CORS errors in Rails 6 Action Text (direct upload of images)
Tags: rails, gcs, googlecloudstorage, cors
Published: 2019-06-02 23:40:28


I recently tried out using Rails 6's new Action Text feature for a simple blog page. One thing I like is about it is how it lets you drag and drop in multiple images and then automatically places them into a nicely formatted "gallery" layout.

When images are placed into the Action Text editor, it performs a direct upload of each image into your storage location via JavaScript. Depending on what you are using for image storage, you may run into some problems caused by CORS (Cross-origin resource sharing) errors.

In my case, I had this problem with Google Cloud Storage. Every time Rails tried to upload a photo through the Action Text edit, I was getting a `No 'Access-Control-Allow-Origin' header is present on the requested resource` CORS error. This is because the browser won't allow the sharing of resources from two different origins, e.g. `my-app.com` and `my-app.storage.googleapis.com`, including sending `POST` requests when trying to upload a photo to storage. (See more on Google Cloud's [CORS page](https://cloud.google.com/storage/docs/cross-origin)).

It took me quite a while to figure out how to fix this, so I wanted to share what I did here!

To solve this problem, you need to modify the CORS configuration for your Google Cloud Storage bucket, which can be done using something called the "gsutil" tool.

The gsutil tool is a CLI that lets you manage Google Cloud Storage buckets from your command line. If you don't have it installed, do so by following [Google's installation instructions](https://cloud.google.com/storage/docs/gsutil_install). You'll also need to login to Google Cloud through the command line with `gcloud init` (also explained on the installation page) or else you won't be able to configure your bucket.

Now you can get to configuring your CORS settings!

To check what the current CORS configuration, use the following command, replacing `my-bucket-name` with the actual name of your bucket.

```
gsutil cors get gs://my-bucket-name
```

This will return some JSON that looks something like this:

```json
[
    {
      "origin": ["http://example.appspot.com"],
      "responseHeader": ["Content-Type"],
      "method": ["GET", "HEAD", "DELETE"],
      "maxAgeSeconds": 3600
    }
]
```

To change/update the CORS configuration, you will first need to create a JSON file that contains your configuration settings. You can name the file whatever you want, but I went with `cors.json`. You can also technically save it anywhere on your computer, but I decided to put mine into my Rails app's `config` directory so I could easily keep track of it and update it if needed.

Now, to solve the problem happening when Action Text attempts to upload images directly to your bucket, you will need to make sure that URL of your app is listed in the JSON's `origin` array, added the `Content-MD5` content type to the `responseHeader` array, and add the `PUT`, `POST`, and `OPTIONS` methods into the `method` array.

The finished `cors.json` file should look like this:

```json
// cors.json

[
  {
    "origin": ["https://my-app.com"],
    "responseHeader": ["Content-Type", "Content-Md5"],
    "method": ["PUT", "GET", "HEAD", "DELETE", "OPTIONS"],
    "maxAgeSeconds": 3600
  }
]
```

To configure the bucket with these settings, use the following command:

```
gsutil cors set cors.json gs://my-bucket-name 
```

Note that the `cors.json` in the above command refers to the location/path of the file. Either `cd` into the same directory or write out the path. (E.g. I had mine in my `config/` directory, so I used `gsutil cors set config/cors.json gs://my-bucket-name` from the app's root directory.

You can check if the CORS configuration was updated by using the `gsutil cors get` command again:

```
gsutil cors get gs://my-bucket-name
```

Note that you may still get CORS errors after updating because the browser may have cached the previous settings. If so, try completely clearing the cache and refreshing the page. Another thing you can try is temporarily setting the `"maxAgeSeconds"` in the `cors.json` to something super short like `1` so that the browser refreshes the response settings immediately (Make sure to change it back later).

If you still have problems, you may need to do some troubleshooting by actually inspecting the response headers using Chrome Developer Tools. Google's [troubleshooting documentation](https://cloud.google.com/storage/docs/configuring-cors#troubleshooting) gives you a good overview of what to look out for.

You can inspect the response headers by opening Dev Tools and navigating to the `Network` tab. Try uploading another image and then find the request to upload the image within the list of network activity. Click the name of the request `Name` column and check the `Headers` section (you may have to check a few different requests before you find the right one). You'll know it's the right one if the `Request URL` starts with `https://storage.googleapis.com/.....`.

Here you want to make sure that the `Origin` within the Request Headers matches exactly one of the origins in your `cors.json`. Also make sure that the `Access-Control-Request-Method` (e.g. `content-md5`) and `Access-Control-Request-Method` (e.g. `PUT` or `OPTIONS`) matches what is in the JSON file. If not, update the JSON file accordingly.

For example, I had a typo in the URL listed for the origin in my `cors.json` file and had to update it. I also noticed later that I had to add the `OPTIONS` method to `method` array (I had only listed `PUT`).

Happy uploading!