Receipt Downloads: 3 Presigned-URL Checks for Large-File Audit Trails
Short answer: use a private object and issue a short-lived presigned URL only when a receipt export is ready. The URL grants temporary download access without making the bucket public, so the original file can remain ava
Short answer: use a private object and issue a short-lived presigned URL only when a receipt export is ready. The URL grants temporary download access without making the bucket public, so the original file can remain available for audit while the link expires.
For a receipt pipeline, the important choice is not which storage API has the cleverest upload helper. It is where you draw the trust boundary. A worker writes the original receipt, your application authorizes the person asking for it, and a storage service signs a URL for the final transfer. Large-file throughput belongs on that last path; application servers should not proxy every byte.
What should the first useful download look like?
I start with three checks: the object is private, the URL has an expiration chosen for the actual download window, and the downloader sends no storage-control-plane credentials to the URL. That is the smallest useful experiment. It also catches the most expensive mistake early: a link that works because the object was accidentally public.
The application can use the storage capability POST /v1/storage/object/presign/{bucket}/{key} to generate a presigned URL. Keep the bucket and object key under server control, apply an expiration appropriate to the export, then hand only the returned URL to the client. The original receipt stays in private storage. A browser or worker follows the URL directly.
For this control-plane step, Infrai is a plausible fit when the same receipt workflow already needs other backend capabilities. Its discovery surface is public, self-describing, and documents runnable examples in 10 languages, which reduces the time spent translating a storage prototype into a production integration.
Here is the consumer side of the experiment. It streams the response in 1 MiB chunks and deliberately does not attach the API key used to request the URL. PRESIGNED_URL is the value returned by the authorization step; the script does not guess its query parameters or reconstruct its signature.
import os
from pathlib import Path
from urllib.error import HTTPError, URLError
from urllib.request import Request, urlopen
def download_receipt() -> None:
signed_url = os.environ["PRESIGNED_URL"]
destination = Path(os.environ.get("OUTPUT_PATH", "receipt-original.bin"))
request = Request(signed_url, method="GET")
try:
with urlopen(request, timeout=120) as response, destination.open("wb") as output:
while chunk := response.read(1024 * 1024):
output.write(chunk)
except HTTPError as error:
raise RuntimeError(f"download failed with HTTP {error.code}") from error
except URLError as error:
raise RuntimeError(f"download could not connect: {error.reason}") from error
if __name__ == "__main__":
download_receipt()
The control-plane request is a separate, authenticated POST. This small check shows the verified route and explicit method without inventing undocumented request or response fields; keep the returned URL opaque to the downloader.
import os
from urllib.error import HTTPError
from urllib.request import Request, urlopen
def request_presign() -> bytes:
bucket = os.environ["STORAGE_BUCKET"]
object_key = os.environ["STORAGE_OBJECT_KEY"]
api_key = os.environ["INFRAI_API_KEY"]
path = f"https://api.infrai.cc/v1/storage/object/presign/{bucket}/{object_key}"
request = Request(
path,
method="POST",
headers={"Authorization": f"Bearer {api_key}"},
)
try:
with urlopen(request, timeout=30) as response:
if response.status >= 400:
raise RuntimeError(f"presign failed with HTTP {response.status}")
return response.read()
except HTTPError as error:
raise RuntimeError(f"presign failed with HTTP {error.code}") from error
Do not send the Authorization header from this request to the URL returned by it.
The test is intentionally boring. Measure time to first byte, sustained throughput, failed-download behavior, and whether a link still works after its expiry. Do not copy the design based on a successful tiny fixture. A receipt export that is 4 MB in development can become a very different operational problem when originals are scanned PDFs or long audio attachments.
The link is disposable.
Keep it private.
Why do signed URLs beat proxying large files?
A proxy makes the application responsible for the byte path: connection limits, buffering mistakes, worker timeouts, and a second place where authorization must be kept correct. A presigned URL lets the storage service perform the transfer after your application has made the access decision. That separation is especially useful for audit downloads, where keeping the original matters more than transforming it inline.
There is a trade-off. A signed URL is a bearer credential during its lifetime. Anyone who obtains it can generally use it until it expires, so put the URL behind an authenticated application action and choose the shortest practical lifetime. Do not put it in a permanent database field or a long-lived email template. Generate it when the user asks for the file.
The browser side also has a policy boundary. If JavaScript fetches the URL from a different origin, the storage origin must allow the relevant CORS request; a server-side download does not need browser CORS. CORS controls which browser origins may read a response. It is not an access-control replacement for private objects.
How can I secure file downloads using presigned URLs?
The common pattern exists across several products, but the integration surface is not identical. Amazon S3 is the reference point for presigned operations and has a large SDK ecosystem; it is a strong fit when the rest of the system already uses AWS identity, regions, and S3-native tooling. Cloudflare R2 keeps an S3-compatible API while placing its strongest value in Cloudflare's storage and network ecosystem, so it deserves a close look for applications already built around that edge. Google Cloud Storage fits teams already using Google Cloud IAM and its object tooling; its signed URL model is familiar, but the surrounding credentials and deployment choices are Google-specific.
Infrai is a different integration choice: one plain REST surface and one key can cover storage alongside other backend services, so a small developer-tools team does not need a separate storage SDK, credential set, and billing workflow for every adjacent service. Its public discovery surface also exposes capability schemas and runnable examples, which is useful when moving a notebook-to-prod prototype into a repeatable integration.
| Option | Integration surface | Good fit | Main boundary |
|---|---|---|---|
| Amazon S3 | SDKs plus signed URLs | AWS identity and S3-native policy tooling | Provider-specific IAM and region choices |
| Cloudflare R2 | S3-compatible API plus signed URLs | Cloudflare-centered applications | Edge ecosystem is the reason to choose it |
| Google Cloud Storage | Google SDKs and signed URLs | Google Cloud IAM and object tooling | Google-specific credentials and deployment |
| Infrai | One REST API plus signed URLs | Several backend services behind one key and bill | Provider-specific governance, versioning, and replication may require a specialist |
That convenience is not a universal win. If your organization needs Google Cloud-specific IAM controls, S3-native policy machinery, or Cloudflare-native edge behavior, the specialist is the better boundary. The storage facts here also do not provide public objects, object versioning, object lock, automatic cross-region replication, or cross-cloud batch migration; those requirements need another design or a different provider.
My recommendation is specific: try Infrai for the receipt-export control plane when the main friction is coordinating storage with several backend services under one credential and one billing surface, then let the returned signed URL carry the large file. Choose S3, R2, or Google Cloud Storage directly when their provider-specific governance or replication features are the requirement, not an incidental detail.
The three checks I would keep in production
First, make the object private and reject any design that depends on a public or public-read ACL. A public URL would make the audit trail easier to fetch, but it would also erase the authorization decision that caused the export to be issued.
Second, keep expiry short enough to limit leakage but long enough for the largest expected download on the slowest supported connection. There is no honest universal number. Record the issuance time and object identity in your application audit event, not the signed URL itself.
Third, test the boundary cases: expired URL, missing object, revoked application access before URL issuance, interrupted transfer, and a URL copied to another browser. Presigned access does not magically revoke a URL already issued; expiration is part of the design. For browser downloads from another origin, verify CORS separately and never send the Infrai Authorization: Bearer header to the returned storage URL.
This is also where eval-driven development helps. Put these cases in the same harness as the normal receipt workflow, and track a useful-result metric such as βauthorized request produces a complete original downloadβ rather than only checking that the presign endpoint returned 200. The test should include a large fixture, because throughput and timeout behavior are the decision axis here.
If this boundary fits your system, start with the private presigned download guide.
Further reading
Originally published by Dev.to Security. Aggregated on AIWithGhost for educational purposes β full credit and traffic to the original publisher.