S3 CORS configuration is surprisingly permissive by default, but enabling it for specific origins requires a delicate touch.
Let’s watch an S3 bucket serve a request from a different domain.
First, we need an S3 bucket, let’s call it my-cross-origin-bucket-12345. Inside, we’ll place a file, say image.jpg.
aws s3 cp image.jpg s3://my-cross-origin-bucket-12345/image.jpg
Now, imagine a simple HTML page hosted on a different domain, http://localhost:8000, trying to load this image using JavaScript. Without CORS, the browser will block this request with a "No 'Access-Control-Allow-Origin' header is present" error.
To allow this, we’ll configure the CORS settings on my-cross-origin-bucket-12345. This is done via an XML policy attached to the bucket.
Here’s what a typical CORS configuration looks like:
<?xml version="1.0" encoding="UTF-8"?>
<CORSConfiguration>
<CORSRule>
<AllowedOrigin>http://localhost:8000</AllowedOrigin>
<AllowedMethod>GET</AllowedMethod>
<AllowedMethod>HEAD</AllowedMethod>
<ExposeHeader>ETag</ExposeHeader>
<MaxAgeSeconds>3000</MaxAgeSeconds>
</CORSRule>
</CORSConfiguration>
This configuration does a few key things:
AllowedOrigin: This is the most crucial part. It specifies which origins (protocol, domain, and port) are permitted to make requests to your S3 bucket. In our example,http://localhost:8000is allowed. You can specify multipleAllowedOriginelements for different origins. A wildcard*can be used, but this is generally discouraged for security reasons as it allows any origin.AllowedMethod: This lists the HTTP methods (likeGET,PUT,POST,DELETE,HEAD) that are allowed from the specified origins.GETandHEADare common for simply retrieving objects.ExposeHeader: By default, browsers hide most response headers for security. If your JavaScript code needs to access specific headers (likeETagfor caching validation), you need to list them here.MaxAgeSeconds: This tells the browser how long it can cache the preflightOPTIONSrequest. A preflight request is sent by the browser before the actual request (e.g.,GET) to check if the server will allow the actual request. Caching this reduces latency for subsequent requests.
You apply this configuration using the AWS CLI:
aws s3api put-bucket-cors --bucket my-cross-origin-bucket-12345 --cors-configuration file://cors-config.xml
Where cors-config.xml contains the XML content shown above.
Once applied, the browser on http://localhost:8000 will receive the Access-Control-Allow-Origin: http://localhost:8000 header in response to its requests to my-cross-origin-bucket-12345, and the image will load successfully.
The browser’s CORS mechanism is a security feature designed to prevent malicious websites from making unauthorized requests to your server on behalf of a logged-in user. S3’s CORS configuration allows you to selectively relax these browser-imposed restrictions for legitimate cross-origin interactions.
The most common mistake people make is using * for AllowedOrigin without fully understanding the security implications, or forgetting to include GET in AllowedMethod when simply trying to read objects.
Another subtlety is that S3’s CORS configuration only controls what the browser is allowed to do. It doesn’t affect direct AWS SDK requests or curl commands, which bypass the browser’s CORS checks entirely.
The next hurdle you’ll likely encounter is handling authenticated requests or more complex scenarios involving preflight OPTIONS requests for methods like PUT or POST, which require specifying AllowedHeaders and Access-Control-Allow-Credentials.