CORS Configuration
Set up CORS rules for bucket access from web apps. Example configurations for common scenarios. Troubleshooting CORS issues.
Cross-Origin Resource Sharing (CORS) allows web applications running in a browser to make requests to your NFYio storage endpoint. Without CORS, browsers block cross-origin requests for security reasons. Configure CORS on your buckets to enable direct uploads, downloads, and API calls from your frontend.
When CORS Is Needed
- Direct browser uploads — User selects a file; JavaScript uploads via
fetchorXMLHttpRequest - Presigned URL usage — Client uses a presigned URL to PUT or GET; the request originates from your domain
- S3 SDK in browser — Using
@aws-sdk/client-s3from a web app
CORS is not required for server-side requests (Node.js, Python, etc.) or when using the AWS CLI.
CORS Rule Structure
Each rule specifies:
- AllowedOrigins — Which domains can make requests (e.g.,
https://app.yourdomain.com) - AllowedMethods — HTTP methods:
GET,PUT,POST,DELETE,HEAD - AllowedHeaders — Request headers the client may send (e.g.,
Content-Type,x-amz-*) - ExposeHeaders — Response headers the client can read
- MaxAgeSeconds — How long the browser may cache the preflight response
Example Configurations
Basic Upload/Download
Allow your web app to upload and download objects:
{
"CORSRules": [
{
"AllowedOrigins": ["https://app.yourdomain.com"],
"AllowedMethods": ["GET", "PUT", "POST", "DELETE", "HEAD"],
"AllowedHeaders": ["*"],
"ExposeHeaders": ["ETag"],
"MaxAgeSeconds": 3600
}
]
}
Multiple Origins (Staging + Production)
{
"CORSRules": [
{
"AllowedOrigins": [
"https://app.yourdomain.com",
"https://staging.yourdomain.com",
"http://localhost:3000"
],
"AllowedMethods": ["GET", "PUT", "POST", "DELETE", "HEAD"],
"AllowedHeaders": ["*"],
"ExposeHeaders": ["ETag", "Content-Length"],
"MaxAgeSeconds": 3600
}
]
}
Presigned URL Upload Only
If you only generate presigned URLs for upload and the client PUTs directly to storage:
{
"CORSRules": [
{
"AllowedOrigins": ["https://app.yourdomain.com"],
"AllowedMethods": ["PUT", "HEAD"],
"AllowedHeaders": ["Content-Type", "Content-Length", "x-amz-*"],
"ExposeHeaders": ["ETag"],
"MaxAgeSeconds": 3600
}
]
}
Multipart Upload
For multipart uploads, the client may send custom headers:
{
"CORSRules": [
{
"AllowedOrigins": ["https://app.yourdomain.com"],
"AllowedMethods": ["GET", "PUT", "POST", "DELETE", "HEAD"],
"AllowedHeaders": [
"Content-Type",
"Content-Length",
"x-amz-*",
"Content-Disposition"
],
"ExposeHeaders": ["ETag", "x-amz-request-id", "x-amz-meta-*"],
"MaxAgeSeconds": 3600
}
]
}
Applying CORS Configuration
AWS CLI
Save the JSON to cors.json and apply:
aws s3api put-bucket-cors \
--bucket my-bucket \
--cors-configuration file://cors.json \
--endpoint-url https://storage.yourdomain.com
API
curl -X PUT "https://storage.yourdomain.com/my-bucket?cors" \
-H "Authorization: AWS4-HMAC-SHA256 ..." \
-H "Content-Type: application/json" \
-d @cors.json
Web Console
- Open Storage → Buckets → select bucket
- Go to Permissions or CORS
- Paste or edit the CORS configuration
- Save
Verifying CORS
Get Current CORS Config
aws s3api get-bucket-cors \
--bucket my-bucket \
--endpoint-url https://storage.yourdomain.com
Test from Browser
Open DevTools (F12) → Console, run:
fetch('https://storage.yourdomain.com/my-bucket/test.txt', {
method: 'GET',
mode: 'cors',
})
.then(r => r.text())
.then(console.log)
.catch(console.error);
Check the Network tab. A successful CORS request will show the response. A failed one may show a CORS error in the console.
Troubleshooting CORS Issues
”No ‘Access-Control-Allow-Origin’ header”
Cause: Your origin is not in AllowedOrigins, or CORS is not configured.
Fix: Add your origin (exact scheme + host + port, e.g., https://app.example.com:443) to AllowedOrigins. Avoid trailing slashes.
”Request header field X is not allowed”
Cause: The client sends a header not listed in AllowedHeaders.
Fix: Add the header to AllowedHeaders or use "*" to allow all (less restrictive). Common headers: Content-Type, Authorization, x-amz-date, x-amz-content-sha256, x-amz-security-token.
Preflight (OPTIONS) Fails
Cause: The server may not respond correctly to OPTIONS, or AllowedMethods doesn’t include the actual method.
Fix: Ensure AllowedMethods includes the method you use (e.g., PUT for upload). MaxAgeSeconds reduces preflight frequency.
Works with curl, Fails in Browser
Cause: Browsers enforce CORS; curl does not.
Fix: Configure CORS on the bucket. Browsers send an OPTIONS preflight before the actual request; the server must respond with appropriate Access-Control-* headers.
Credentials (Cookies, Auth Headers)
If sending credentials (credentials: 'include' or withCredentials: true):
AllowedOriginscannot use*; specify exact origins- Ensure the storage endpoint supports credentials (cookies/same-origin or proper CORS with credentials)
For presigned URLs, credentials are in the URL, so this is often unnecessary.
Security Considerations
- Avoid
*for AllowedOrigins — Use specific domains to prevent other sites from using your bucket - Limit AllowedMethods — Only allow methods you need
- Use HTTPS — Configure CORS only for HTTPS origins in production
Next Steps
- Presigned URLs — Generate upload URLs for browser clients
- Working with Objects — Upload patterns from the browser
- Access Keys — Credentials for server-side presigned URL generation