The way I solved auth in my case was just proxying everything through my backend and having that do the auth. I have my own URL scheme and the users never see the URL for the file in S3.
Another way you can do it is generating pre-signed URLs in your backend on each request to download something... but the URL that is generated when you do that is only valid for some small time period, so not a stable URL at all.
In my use case, I needed stable URLs, so I went the proxy route.
Yes, for some use cases proxying is fine. For larger resources or heavily loaded servers (servers I don't want to scale) I prefer to avoid it.
Thank you.