DEV Community

Felice Forby
Felice Forby

Posted on

Debugging Google Cloud Storage CORS errors in Rails 6 Action Text (direct upload of images)

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).

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. 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
Enter fullscreen mode Exit fullscreen mode

This will return some JSON that looks something like this:

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

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:

// cors.json

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

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

gsutil cors set cors.json gs://my-bucket-name 
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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 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!

Oldest comments (5)

Collapse
 
faqndo97 profile image
Facundo Espinosa

I was like two days trying to understand why I couldn't upload photos from my action text. You saved me, thanks!!!!!!!

Collapse
 
morinoko profile image
Felice Forby

It took me FOREVER to figure out what was going on too! I'm glad it helped :D

Collapse
 
chickensmitten profile image
chickensmitten

You saved me a lot of days. Thank you so much. ~<3

Collapse
 
morinoko profile image
Felice Forby

Yay, I'm glad it helped!

Collapse
 
eugenioantoniobonanno profile image
Gino

Thanks a ton for this, was a big help. I had to make a small adjustment and set the response headers slightly differently but this led me down the right path. Props.

For anyone else still sturggling this is the change I had to make:

"responseHeader": ["Origin", "Content-Type", "Content-MD5", "Content-Disposition"],