A browser upload to S3 fails with CORS
After this you can answer: How do you distinguish an S3 CORS failure from a signed upload permission failure?
Symptom
Section titled “Symptom”Troubleshooting scenario: a poster upload fails in the browser. S3’s OPTIONS response says “This CORS request is not allowed.” CORS controls which website origins a browser may read responses from; it does not grant access to private objects.
Read the error first
Section titled “Read the error first”The browser enforces CORS; S3 answers the preflight, an OPTIONS request asking whether the planned PUT is allowed from this origin. Reach succeeded if S3 replied. This is a browser cross-origin check, not a password. If OPTIONS passes and PUT fails, check Allowed/signature separately: IAM missing allow and explicit deny are authorization failures, not CORS fixes.
Decide
Section titled “Decide”flowchart TB A["Browser upload failed"] --> B{"OPTIONS succeeded?"} B -->|"no"| C["Match origin, PUT and requested headers"] B -->|"yes"| D{"PUT succeeded?"} D -->|"no"| E["Read S3 signature or authorization error"] D -->|"yes"| F["Check exposed headers and completion API"]Likely causes
Section titled “Likely causes”Ranked for the quoted preflight rejection.
- The page’s scheme, hostname or port differs from the configured
appUrlorigin, perhaps after an ALB/CloudFront mode change. - The requested method or header is absent from the bucket CORS rule.
- The preflight passed, but a PUT authorization error is hidden from script by missing CORS response headers. Inspect both requests rather than trusting the console summary.
How to check
Section titled “How to check”Browser Network → OPTIONS: compare Origin, Access-Control-Request-Method and
Access-Control-Request-Headers with S3 → bucket → Permissions → CORS. Then inspect PUT.
Do not copy its signed query string into a report.
Set ASSET_BUCKET to the bucket’s name from S3; this reads the deployed rule.
export AWS_PROFILE=ticketliteaws s3api get-bucket-cors --bucket "$ASSET_BUCKET"A successful CLI object request would not prove browser CORS works: the CLI does not enforce it.
Search PutBucketCors and DeleteBucketCors management events for configuration changes.
CloudTrail does not record the browser’s policy decision. Use Network for OPTIONS and PUT;
object calls need S3 data-event logging for a CloudTrail record.
Align createAssetCors with the actual web origin. Current rules allow GET/PUT and
content-type, content-length, x-amz-server-side-encryption; expose ETag, cache preflight
120 seconds. Keep the upload hook’s returned signed headers and credentials: "omit".
Prevent
Section titled “Prevent”Run a browser poster-upload check after origin/mode changes. Verify OPTIONS, PUT and API publication, while also testing an unauthorized origin. Broadening IAM cannot repair a preflight.
infra/src/tickets/cors.ts → createAssetCors;
Inspect bucket CORS →.
Say it in 30 seconds
Section titled “Say it in 30 seconds”Advanced: Three different checks in one upload
The authorized start API gives an admin a short-lived capability. S3 CORS lets the browser
perform its cross-origin request. S3 signature and policy checks authorize the object PUT.
A successful PUT still does not publish a poster: the completion API verifies stored bytes,
then copies to a separate published key. All three stages need evidence.
S3 handles OPTIONS from the CORS configuration; allowedMethods lists PUT rather than OPTIONS.
See AWS’s CORS troubleshooting rules.
apps/web/src/features/events/hooks/use-upload-poster.ts → useUploadPoster;
apps/api/src/services/posters.ts → complete.
Read this book online: https://book.theversehub.in/