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 URLconst 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:OPTIONSStatus Code:403 ForbiddenRemote Address:<IP>:443Referrer Policy:strict-origin-when-cross-origin
RESPONSE HEADERS:
Content-Type:application/xmlDate:Thu, 16 May 2024 01:03:33 GMTServer:AmazonS3Transfer-Encoding:chunkedX-Amz-Id-2:<ID>X-Amz-Request-Id:<ID>
REQUEST HEADERS:
Accept:*/*Accept-Encoding:gzip, deflate, br, zstdAccept-Language:en-US,en;q=0.9Access-Control-Request-Method:PUTCache-Control:no-cacheConnection:keep-aliveHost:mybucket.s3.ap-southeast-2.amazonaws.comOrigin:http://localhost:3000Pragma:no-cacheReferer:http://localhost:3000/Sec-Fetch-Dest:emptySec-Fetch-Mode:corsSec-Fetch-Site:cross-siteUser-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.
Update 1
When I test the PUT command in Postman, I get the following error which is different to the CORs error:
<?xml version="1.0" encoding="UTF-8"?><Error><Code>PermanentRedirect</Code><Message>The bucket you are attempting to access must be addressed using the specified endpoint. Please send all future requests to this endpoint.</Message><Endpoint>s3.amazonaws.com</Endpoint><Bucket>mybucket</Bucket><RequestId>__ID__</RequestId><HostId>__ID__</HostId></Error>