Scenario:
- Client (Nextjs client component) queries backend (NextJS rest endpoint) for pre-signed URL
- Backend gets presigned URL for S3 via
getSignedUrl
import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3";
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
import { env } from "@/env";
const AWS_REGION = "ap-southeast-2";
const s3Client = new S3Client({
region: AWS_REGION,
credentials: {
accessKeyId: env.AWS_S3_ACCESS_ID,
secretAccessKey: env.AWS_S3_ACCESS_KEY,
},
});
const command = new PutObjectCommand({
Bucket: s3Object.bucket,
Key: s3Object.key,
// I also tried passing the contenttype here to see if it changed anything. But no it didn't.
// ContentType: s3Object.contentType,
// Might need more fields here to refine...
});
// Calculate dynamic expiration time based on file size, user's internet connection speed, etc.
const expiresIn = calculateExpirationTime(s3Object);
const presignedUrl = await getSignedUrl(s3Client, command, { expiresIn });
return presignedUrl;
- Client receives presigned URL from backend
- Client sends file chunk to presigned URL
// Uploads a chunk to the presigned URL
const uploadPart = (opts: {
url: string;
chunk: Blob;
contentType: string;
chunkSize: number;
fileName: string;
maxRetries: number;
}) =>
fetch(opts.url, {
method: "PUT",
body: opts.chunk,
//headers: {
// "Content-Type": opts.contentType,
//},
})
- Frontend throws a CORs error:
Access to fetch at 'https://mybucket.s3.ap-southeast-2.amazonaws.com/mydoc.pdf?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=AMZ_VALUE%2Fap-southeast-2%2Fs3%2Faws4_request&X-Amz-Date=20240516T010037Z&X-Amz-Expires=14400&X-Amz-Signature=SIGNATURE&X-Amz-SignedHeaders=host&x-id=PutObject' from origin 'http://localhost:3000' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
OPTIONS request is sent and comes back as 403 Forbidden
Request Method:
OPTIONS
Status Code:
403 Forbidden
Remote Address:
<IP>:443
Referrer Policy:
strict-origin-when-cross-origin
RESPONSE HEADERS:
Content-Type:
application/xml
Date:
Thu, 16 May 2024 01:03:33 GMT
Server:
AmazonS3
Transfer-Encoding:
chunked
X-Amz-Id-2:
<ID>
X-Amz-Request-Id:
<ID>
REQUEST HEADERS:
Accept:
*/*
Accept-Encoding:
gzip, deflate, br, zstd
Accept-Language:
en-US,en;q=0.9
Access-Control-Request-Method:
PUT
Cache-Control:
no-cache
Connection:
keep-alive
Host:
mybucket.s3.ap-southeast-2.amazonaws.com
Origin:
http://localhost:3000
Pragma:
no-cache
Referer:
http://localhost:3000/
Sec-Fetch-Dest:
empty
Sec-Fetch-Mode:
cors
Sec-Fetch-Site:
cross-site
User-Agent:
<AGENT>
S3 Bucket Permissions
I have configured this correctly. I have tried many different variations including explicitly defining localhost:3000 in the AllowedOrigins.
CORS settings:
[
{
"AllowedHeaders": [
"*"
],
"AllowedMethods": [
"GET",
"PUT",
"POST",
"DELETE",
"HEAD"
],
"AllowedOrigins": [
"*"
]
}
]
Policy attached to the user
The user who is generating the presigned URL has the following policy attached:
{
"Version": "2012-10-17",
"Statement": [
{
"Action": [
"s3:GetObject",
"s3:GetObjectVersion",
"s3:PutObject"
],
"Resource": [
"arn:aws:s3:::mybucket",
"arn:aws:s3:::bmybucket/*"
],
"Effect": "Allow"
}
]
}
I have been struggling with this one for quite some time and tried so many of the suggestions from around SO and Google. None have worked. I have a feeling I have missed some small detail and it’s causing me all this grief.