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:
AllowedHeader
- Values to return in the Access-Control-Request-Headers response header for preflight requests. This is optional.AllowedMethod
- Values to return in the Access-Control-Allow-Methods response header for preflight requests.AllowedOrigin
- Value to return in the Access-Control-Allow-Origin response header.ExposeHeader
- Values to return in the Access-Control-Expose-Headers response header. This is optional.ID
- An optional identifier for the rule, limited to 255 characters. This is optional.MaxAgeSeconds
- Value to return in the Access-Control-Max-Age response header for preflight requests.
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.