How to upload to S3 securely without touching your bucket
By Aleem Rehmtulla, May Inc.
TL;DR
Never ship AWS access keys to the browser. S3 Viewer keeps the key on the server and streams every upload through its own API into your bucket — the browser only ever talks to S3 Viewer, so there is no CORS rule, no exposed header and no bucket setting to configure. Presigned URLs are the classic alternative for your own apps; S3 Viewer still uses them for downloads, where they need no bucket changes either.
Steps
Step-by-step.
- 01
Why not put the AWS key in the browser?
Anything in your browser bundle is public — your access key ends up readable to anyone who opens DevTools. Even with scoped IAM, leaking the key lets attackers do anything allowed by that IAM policy until you rotate. Presigned URLs avoid the problem entirely by signing on the server and handing the browser only a short-lived URL. - 02
How S3 Viewer uploads: browser → S3 Viewer → bucket
Smaller files use a single upload request to the S3 Viewer API. For larger files, the browser coordinates a multipart upload with separate requests to the API for each part. Both paths check your session and role and stream the bytes into the bucket using the server's credential, without writing object contents to disk. Interrupted multipart uploads can be resumed; cancellation requests cleanup on a best-effort basis. Your access key stays encrypted at rest and is decrypted only in memory to sign requests. - 03
Nothing to configure on the bucket
Because the browser never talks to S3 directly, the bucket needs no CORS policy, no exposedETagheader and no public-access change. Add a server with a least-privilege key and uploads work on AWS S3, Cloudflare R2, MinIO and any other S3-compatible provider exactly as they are. Individual downloads and media previews go from your bucket to the browser through 15-minute presigned links — plain navigations and<img>loads, which need no CORS either. - 04
Background: presigned URLs for your own app
If you are building your own uploader, the classic pattern is a presigned URL: your backend callsgetSignedUrlwithPutObjectCommand, the browser PUTs the file to that URL and the key never leaves the server. The trade-off is that the bucket must then allow your web origin in its CORS policy — which is exactly the setup step S3 Viewer avoids by proxying.import { getSignedUrl } from '@aws-sdk/s3-request-presigner'; import { PutObjectCommand } from '@aws-sdk/client-s3'; const url = await getSignedUrl( s3, new PutObjectCommand({ Bucket: 'my-bucket', Key: 'uploads/' + filename, ContentType: contentType, }), { expiresIn: 900 }, // 15 minutes ); - 05
Background: restrict a presigned URL further
You can lock down the presigned URL with conditions: specific content-type, max size, exact key, expiry, source IP. Addingx-amz-meta-*conditions enforces metadata at upload time. The narrower the URL, the less damage if it leaks before expiry.
Under the hood
What's actually happening.
Smaller files arrive at the S3 Viewer API in a single request, whose body the API passes to the AWS SDK's streaming uploader. Larger files use browser-coordinated multipart uploads: the browser starts the upload, sends parts in separate requests through the API, and requests completion. It can retry individual parts and resume an interrupted upload while the uploaded parts remain available. Both paths enforce session and role checks, decrypt credentials only in memory, and avoid storing object contents on disk. Failed single-request uploads and cancelled multipart uploads trigger best-effort cleanup; incomplete parts can remain and incur storage charges, so a bucket lifecycle rule is a useful backstop. Individual-object downloads take the other route: the API signs a 15-minute presigned GetObject URL and the browser loads it directly from the bucket. The URL allows repeated GET requests for that object until it expires, subject to credential validity and bucket permissions.
FAQ
Common questions.
How do I upload to S3 without exposing my AWS key?
Are presigned URLs secure?
Does S3 Viewer need a CORS policy on my bucket?
How large a file can I upload through S3 Viewer?
Do presigned URLs work with Cloudflare R2?
How short should a presigned URL's expiry be?
Use S3 Viewer for this
Skip the CLI. Try it in the browser.
S3 Viewer turns the steps above into a single click. Open source, self-hostable, free.
Use case
Why teams pick this
Related guides
More how-tos
Upload large files
How large uploads work in S3 Viewer and from the CLI — streaming, retries, and the 5 GB single-PUT cap.
Share an S3 file
Copy link for a 15-minute presigned URL, or an email invite for ongoing access — when each is the right call.
Download a file
Browser, AWS CLI, or presigned URL — three ways, with the object's own filename and zero key exposure.