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 fetch or XMLHttpRequest
  • 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-s3 from 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

  1. Open StorageBuckets → select bucket
  2. Go to Permissions or CORS
  3. Paste or edit the CORS configuration
  4. 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):

  • AllowedOrigins cannot 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