Browser Object Upload Control Plane With Presigned PUT and Private Signed Downloads
The operational constraint is not how quickly an application can mint a URL. It is whether the signing path, browser policy, object namespace, and cleanup process still behave predictably when uploads surge and clients retry. Short answer: keep the bucket private, let the application authorize a narrowly scoped upload and return a short-lived presigned PUT URL, send the file from the browser…
The operational constraint revolves around the unpredictability of URL generation, signing, browser policy, object namespace, and deletion procedures when file uploads intensify and clients repeat the process. The optimal solution is to maintain bucket privacy, delegate application authorization for narrowly scoped uploads, return temporary presigned URLs for PUT operations, and generate distinct brief signed download URLs post-application verification of object records.
Consider signing as a control plane governed by an SLO. Files should remain within the Node.js or Express process, never crossing over to the data plane. This approach aligns well with a React client and an Express authorization route, although the example below uses Go to illustrate protocol boundaries distinctly: the browser obtains a URL, a method, necessary headers, an object identifier, and an expiry, but not the storage credentials.
What ought a React, Node.js, and Express browser upload contract encompass? A direct-to-storage design entails four stages. Initially, React seeks upload authorization from the application using the user's existing session. Subsequently, Express applies application policy, scrutinizing identity, object size, media type, quota, and destination prefix.
Thirdly, the browser executes the presigned PUT against the provided storage URL. Lastly, the application logs completion solely after confirming the expected object's existence. An affirmative authorization response does not validate a completed upload. Preserve the contract's simplicity. A beneficial response should include object_key, upload_url, method, required_headers, and expires_at.
The method must consistently be set to PUT, while the required headers should be replicated verbatim into the browser's request. The application should generate the object key; granting clients arbitrary bucket keys converts a storage primitive into a namespace-policy circumvention. The browser's flow is uncomplicated: request authorization, carry out a PUT fetch using the supplied URL, headers, and file body, and relay the outcome to the application using the opaque object identifier.
This final notification serves as a hint, not a confirmation. Do not mark durable business state complete simply because React reports the PUT returned success. One factor causes significant confusion. The upload request is cross-origin, potentially triggering a CORS preflight prior to PUT. The documented browser behavior employs an OPTIONS request carrying the intended method and headers; the storage origin must confirm policy permitting the web origin, PUT, and all non-safelisted request headers.
CORS is purely browser enforcement, not upload authentication, and rendering the bucket public does not rectify a flawed preflight policy. Construct the signing path as a compact control plane. The authorization handler should rely on a signer interface rather than a vendor SDK throughout the application. This ensures testable policy implementation and maintains future storage migration visibility.
Additionally, it makes the operational boundary apparent: the signer crafts capability URLs, while a metadata store manages application state.
Written by urgent.news from Dev.to's reporting — not their text. Machine-written — may contain errors; check the original before relying on it.