Configuring CORS on Cloudflare R2

Configuring CORS on Cloudflare R2

Not being able to configure Cross-Origin Resource Sharing (CORS) has been a thorn in the side of many developers.

Whilst you could work around it using Transform Rules for public buckets, a common pain point is needing CORS to be able to use presigned URLs for client uploads.

In yesterday's (27-09-2022) release, R2 gained support for the <Verb>BucketCors operations such as PutBucketCors. This means that you can now apply the same CORS rules that you would for AWS S3 onto R2 buckets.

How to write a CORSRule

R2's CORS implementation currently takes the form of AWS S3's CORSRule. Whilst S3's web console has these in the form of a JSON policy, R2 handles it through the REST API and therefore XML.

<?xml version="1.0" encoding="UTF-8"?>
<CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
    <CORSRule>
        <AllowedMethod>GET</AllowedMethod>
        <AllowedMethod>HEAD</AllowedMethod>
        <AllowedOrigin>*</AllowedOrigin>
    </CORSRule>
</CORSConfiguration>

This is an example rule that would allow GET and HEAD requests from any origin. You can have up to 100 CORSRule elements in the configuration for a bucket.

A CORSRule element can contain the following:

You can specify multiple CORSRule elements with different origins to return a different configuration depending on the origin. If you'd like to learn more, take a look at CORS configuration in AWS's documentation.

Allowing cross-origin PutObject

For this example, we'll be enabling PUT requests so that presigned URLs can be used as a client-side upload method. Historically, browsers didn't support this due to the lack of CORS.

If I attempt to upload to a presigned PutObject URL from a browser, without any CORS configuration, I'll receive a 403 response.

Let's add the CORS configuration to the bucket and give it another try - whilst there's functions in most of the S3 SDKs to do this, we'll just do it ourselves with the API.

The URL you're going to be sending my requests to is https://<BUCKET-NAME>.<ACCOUNT-ID>.r2.cloudflarestorage.com/?cors - you can also find this in the overview for that bucket in the Cloudflare dashboard.

The URL in the dashboard uses a path-style (with the bucket name in the path) as opposed to a virtual-host style (with the bucket name in the hostname) but they function exactly the same.

<?xml version="1.0" encoding="UTF-8"?>
<CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
    <CORSRule>
        <AllowedMethod>PUT</AllowedMethod>
        <AllowedOrigin>*</AllowedOrigin>
        <AllowedHeader>Content-Type</AllowedHeader>
        <AllowedHeader>Content-Length</AllowedHeader>
    </CORSRule>
</CORSConfiguration>

This is the request body we'll be sending to allow PUT requests from all origins - you can modify this to your liking.

To apply the configuration, send a PUT request to https://<BUCKET-NAME>.<ACCOUNT-ID>.r2.cloudflarestorage.com/?cors with the CORS configuration in the request body. If all went well, you should get a 200 OK response code back.

I'll be using Postman - make sure that you've setup your S3 API credentials too.

It should look like this:

In the Body tab, set it to raw and change the type to XML:

Press Send and so long as you get a 200 OK response back, it's good to go!

Let's try that upload again.

There we go - we've got proper CORS handling on our bucket. The CORS configuration affects the S3 API and any of your public or custom domains - so you might also want to add GET into the allowed methods if you're planning to serve assets for your website from a public bucket.