Search code examples
ruby-on-railsrubysecurityrails-activestorage

How to serve attachments privately using Rails?


I am making a RESTful API using Ruby on Rails. I use Active Storage to manage attachments. However, Active Storage serves attachments publicly, and I want to serve privately.

I had been thinking to serve them on a route like /assets/:name?token=TOKEN. So I can manage them this way on the front-end for example:

// Front-end
const flowerPhoto = new Image
const params = new URLSearchParams({ token })
image.src = `${API_URL}/assets/flower-photo?${params.toString()}`

How can I achieve this?

I found this part Serving Files of Rails Guides very confusing

All Active Storage controllers are publicly accessible by default. The generated URLs are hard to guess, but permanent by design. If your files require a higher level of protection consider implementing Authenticated Controllers.

The RedirectController redirects to the actual service endpoint. This indirection decouples the service URL from the actual one, and allows, for example, mirroring attachments in different services for high-availability. The redirection has an HTTP expiration of 5 minutes.

I had tried it this way:

# config/routes.rb
Rails.application.routes.draw do
  scope 'assets/:name', as: 'asset' do
    root 'assets#show', as: ''
  end
end
# app/models/asset.rb
class Asset < ApplicationRecord
  has_one_attached :document
end
# app/controllers/assets_controller.rb
class AssetsController < ApplicationController
  include ActiveStorage::SetCurrent

  before_action :set_asset
  before_action :authorize

  def show
    redirect_to @asset.document.url
  end

  private

  def set_asset = @asset = Asset.find_by!(name: params[:name])

  def authorize = user_authentication(params[:token])
end
# config/application.rb
module App
  class Application < Rails::Application
    # Public attachment URLs
    config.active_storage.draw_routes = false
  end
end

Nonetheless, I get this error:

NoMethodError in AssetsController#show
undefined method `rails_disk_service_url' for module
redirect_to @asset.document.url

Solution

  • Delete the configuration you have set, and then:

    redirect_to @asset.document.url(expires_in: 5.minutes, disposition: 'inline')