β οΈ Common Problem
If youβre uploading files to AWS S3 directly from the browser using a pre-signed URL (via JavaScript and a .NET Core API), youβve probably faced this frustrating error:
Access to fetch at 'https://your-bucket.s3.amazonaws.com/...'
from origin 'http://localhost:3000' has been blocked by CORS policy:
No 'Access-Control-Allow-Origin' header is present on the requested resource.
This happens because your S3 bucket does not allow requests from your frontend domain.
In this article, weβll fix it step-by-step for both AWS S3 and your .NET Core API, with real working code and links to official AWS docs.
π§ What is CORS?
CORS (Cross-Origin Resource Sharing) is a security feature of browsers that blocks JavaScript code from making requests to another domain unless explicitly allowed.
When your frontend (localhost:3000
or myapp.com
) tries to upload to S3 (s3.amazonaws.com
), the browser asks:
βDoes this S3 bucket allow requests from my origin?β
If not, the request is blocked β even before reaching AWS.
π§± Step 1. Configure CORS in Your AWS S3 Bucket
1οΈβ£ Go to your AWS S3 Console
2οΈβ£ Select your bucket β Permissions tab β Scroll to Cross-origin resource sharing (CORS)
3οΈβ£ Click Edit, and paste the following configuration:
[{
"AllowedHeaders": ["*"],
"AllowedMethods": ["GET", "PUT", "POST"],
"AllowedOrigins": ["http://localhost:3000", "https://yourdomain.com"],
"ExposeHeaders": ["ETag"],
"MaxAgeSeconds": 3000}]
π§Ύ Explanation
Key | Description |
---|
AllowedOrigins | Domains allowed to access the bucket (add your frontend URLs) |
AllowedMethods | HTTP methods allowed (PUT for upload, GET for fetch) |
AllowedHeaders | Required when sending custom headers |
ExposeHeaders | Lets browser read specific headers (e.g., ETag) |
MaxAgeSeconds | Cache time for preflight results |
π Official AWS Docs: Configuring CORS for S3
βοΈ Step 2. Generate Pre-Signed URL in .NET Core API
Create an endpoint in your .NET Core project to generate a pre-signed URL:
using Amazon.S3;
using Amazon.S3.Model;
using Microsoft.AspNetCore.Mvc;
using System;
namespace AwsUploadDemo.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class S3UploadController : ControllerBase
{
private readonly IAmazonS3 _s3Client;
private const string BucketName = "your-bucket-name";
public S3UploadController(IAmazonS3 s3Client)
{
_s3Client = s3Client;
}
[HttpGet("generate-url")]
public IActionResult GeneratePresignedUrl(string fileName)
{
var key = $"uploads/{Guid.NewGuid()}_{fileName}";
var request = new GetPreSignedUrlRequest
{
BucketName = BucketName,
Key = key,
Verb = HttpVerb.PUT,
Expires = DateTime.UtcNow.AddMinutes(10),
ContentType = "image/jpeg"
};
var url = _s3Client.GetPreSignedURL(request);
return Ok(new
{
uploadUrl = url,
fileUrl = $"https://{BucketName}.s3.amazonaws.com/{key}"
});
}
}
}
π This API safely generates a short-lived pre-signed upload URL that your frontend can use.
π₯οΈ Step 3. Upload File from JavaScript (Frontend)
Hereβs a minimal HTML + JavaScript example:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>AWS S3 CORS Fix Example</title>
</head>
<body>
<h2>Upload Image to AWS S3</h2>
<input type="file" id="fileInput" accept="image/*" />
<button id="uploadBtn">Upload</button>
<p id="msg"></p>
<script>
document.getElementById("uploadBtn").addEventListener("click", async () => {
const file = document.getElementById("fileInput").files[0];
if (!file) return alert("Please choose a file!");
// Step 1: Ask API for presigned URL
const res = await fetch(`https://localhost:5001/api/S3Upload/generate-url?fileName=${file.name}`);
const data = await res.json();
// Step 2: Upload directly to S3
const uploadRes = await fetch(data.uploadUrl, {
method: "PUT",
headers: { "Content-Type": file.type },
body: file
});
if (uploadRes.ok) {
document.getElementById("msg").innerText = "β
Upload Successful!";
console.log("File URL:", data.fileUrl);
} else {
document.getElementById("msg").innerText = "β Upload Failed!";
}
});
</script>
</body>
</html>
β
Once your S3 CORS settings are correct, the upload will succeed directly from the browser.
π Step 4. Enable CORS in .NET Core API (Optional)
If your frontend and backend run on different ports (e.g., React on 3000 and API on 5001), enable CORS in your .NET API as well.
In Program.cs
:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddCors(options =>
{
options.AddPolicy("AllowFrontend", policy =>
{
policy.WithOrigins("http://localhost:3000", "https://yourdomain.com")
.AllowAnyHeader()
.AllowAnyMethod();
});
});
builder.Services.AddControllers();
builder.Services.AddAWSService<IAmazonS3>();
var app = builder.Build();
app.UseCors("AllowFrontend");
app.MapControllers();
app.Run();
π§ͺ Step 5. Test and Verify
Run your .NET Core API (https://localhost:5001
)
Serve your frontend app (http://localhost:3000
)
Select a file and click Upload
β
The file should appear in your S3 bucket β uploads/ folder.
β
The browser console should show no CORS errors.
π§Ύ Common Mistakes & Fixes
Issue | Fix |
---|
β Still getting CORS error | Ensure AllowedOrigins includes exact domain, not * if using PUT . |
β 403 Forbidden | Check IAM user permissions (s3:PutObject and bucket policy). |
β Wrong Content-Type | Match ContentType between .NET API and JavaScript upload request. |
β Mixed HTTP/HTTPS | Always use the same scheme (e.g., both HTTPS). |
π References
π AWS Docs β Configure CORS for S3
π .NET AWS SDK for S3
π MDN Web Docs β CORS Explained
π Fix CORS Error in JavaScript Fetch
π Summary
Step | Description |
---|
1οΈβ£ | Add proper CORS JSON configuration in your S3 bucket |
2οΈβ£ | Create pre-signed URL API in .NET Core |
3οΈβ£ | Upload file directly from browser using fetch() |
4οΈβ£ | Enable CORS in your .NET API if needed |
5οΈβ£ | Test and confirm successful upload without CORS error |
β
Now your S3 uploads will work seamlessly from any frontend β React, Angular, or even plain HTML β with no CORS errors and secure access via pre-signed URLs.