ruby 69 lines · 4 tabs

Signed, Expiring S3 Download URLs for Active Storage Attachments in Rails

Shared by codesnips Jul 2026
4 tabs
class DownloadsController < ApplicationController
  before_action :authenticate_user!

  def show
    report = current_account.reports.find(params[:report_id])
    authorize! :download, report

    unless report.export_ready?
      return head :not_found
    end

    url = report.signed_download_url(:export, expires_in: 2.minutes)

    render json: {
      url: url,
      filename: report.export.filename.to_s,
      expires_at: report.signed_download_expiry(expires_in: 2.minutes).iso8601
    }
  end
end
4 files · ruby Explain with highlit

Active Storage attachments are usually served through Rails' proxy or redirect controllers, but many applications need a short-lived, tamper-proof link that can be handed to a browser, an email, or a third-party service without exposing the underlying storage credentials. This snippet shows how to mint such links using the disk service's built-in signed IDs and, for cloud backends like S3, the service's native presigned URL support.

In SignedDownloads concern, the logic is packaged as a model concern so any record with an attachment can expose a consistent API. The signed_download_url method delegates to url on the blob, passing an expires_in window and a disposition so the browser is told to download rather than render inline. The Content-Disposition filename is derived from the blob so the saved file keeps a sensible name. Because the URL is generated by the storage service, when the backend is S3 the result is a real AWS presigned URL whose signature encodes the expiry — the link simply stops working after the window closes, with no server round-trip required to revoke it.

The DownloadsController wires this into an HTTP endpoint. It authorizes the current user against the owning record before ever touching the blob, which matters because a signed URL is a bearer token: anyone holding it can download the file until it expires. Handing out the shortest viable expires_in limits the blast radius if a link leaks. The controller responds with JSON containing the URL and its expiry, which is convenient for single-page apps that fetch the link and then trigger the download client-side.

In ExportReadyMailer, the same concern feeds an email. Email links tend to sit in inboxes for a long time, so a longer expiry (12.hours) is used deliberately, illustrating the trade-off between convenience and exposure. A subtle pitfall is regenerating URLs on every render: because each call produces a fresh signature, links embedded earlier still work independently, but callers should avoid caching a presigned URL past its own expiry. This pattern keeps storage private-by-default while granting narrow, time-boxed access exactly where it is needed.


Related snips

Share this code

Here's the card — post it anywhere.

Signed, Expiring S3 Download URLs for Active Storage Attachments in Rails — share card
Link copied