The example code uses a 2-phase fetch, i.e. the frontend first downloads the signed URL, processes it with JavaScript, and finally sends another request to the bucket by inserting an <img>
tag, setting the window.location
, or sending another fetch
. This solution works and it offers less error-prone configurations especially when it comes to CORS.
const req = await fetch("/api/${id}/image");
if (!req.ok) {
throw new Error("download failed");
}
const {url} = await req.json();
img.src = await req.json();
Instead of a single <img src="...">
, it now requires JavaScript code. Because of this, adopting signed URLs is not transparent to the frontend.
This is usually not a problem for new applications, as you can engineer the frontend this way. But what if you want to migrate an existing backend to use signed URLs? If the frontend is a huge legacy application, it might not be easy to rewrite the affected parts to use the 2-phase fetch.
Fortunately, there is a solution for this right in the HTTP specification: temporary redirects.
Instead of sending back the signed URL in the response body, the backend can return a redirect code and the new location in a header. The browser then automatically follows the link, no JavaScript necessary.
Using temporary redirects, the backend can be migrated to use signed URLs without even touching the frontend.
But unlike the 2-phase fetch, it brings a few additional complications that you need to be aware of.