The middle ground
Your app stores each user's invoices in a private bucket. The user clicks Download. Now what? The bucket must stay private, but this one person needs this one file, right now.
Bad option 1: make the bucket public. Lesson 4-2 covered how that ends.
Bad option 2: stream the file through your own server, private bucket → server → user. It works, but every download now costs your server memory and bandwidth, twice.
The S3 answer is a presigned URL. Your server, which does have S3 access through its role (lesson 2-3), uses its credentials to sign a URL that says: whoever holds this exact link may GET this exact object until this exact time. The user's browser then downloads straight from S3. The signature is part of the URL, so S3 can verify it without the user having any AWS identity at all.
Code exercise · bash
S3's expiry check is simple arithmetic on timestamps (seconds). A URL was signed at time 800 with a 300-second lifetime, and a request arrives at time 1000. Run the check S3 performs.
Code exercise · bash
Your turn. A user bookmarked a presigned link signed at time 800 with a one-hour lifetime (3600 seconds) and clicks it at time 5000. Update the variables and predict the outcome before running.
Quiz
Presigned URLs also work for uploads (PUT). Why would an app hand the browser a presigned upload URL instead of accepting the file itself and forwarding it to S3?