Posted over 10 years ago. Visible to the public.

Deliver Paperclip attachments to authorized users only

When Paperclip attachments should only be downloadable for selected users, there are three ways to go.
The same applies to files in Carrierwave.

  1. Deliver attachments through Rails

The first way is to store Paperclip attachments not in the default public/system, but in a private path like storage inside the current release. You should prefer this method when dealing with sensitive data.

Make sure that folder is linked to the current release when deploying by adding this to your config/deploy.rb:

namespace :paperclip do desc "Create a storage folder for Paperclip attachment in shared path" task :create_storage do run "mkdir -p #{shared_path}/storage" end desc "Link the Paperclip storage folder into the current release" task :link_storage do run "ln -nfs #{shared_path}/storage #{release_path}/storage" end end before "deploy:setup", 'paperclip:create_storage' after "deploy:update_code", "paperclip:link_storage"

Also make sure that storage is ignored in your .gitignore or mayhem will ensue.

Now write a controller action that sends the attachment to authorized users. That action should be protected by Consul or a before_action.

class NotesController power :notes # Authorization through Consul def attachment note = Note.find(params[:id]) send_file note.attachment.path end end

Connect the action in your config/routes.rb:

map.resources :notes, :member => { :attachment => :get }

Your attachment can be configured like this:

class Note < ActiveRecord::Base has_attached_file :attachment, :path => ":rails_root/storage/:rails_env/attachments/:id/:style/:basename.:extension" end

To link to the protecting controller actions, e.g. in a view:

link_to 'Download attachment', [:attachment, @note]
  1. Store attachments in hashed paths

The method above is very secure, but it hits the framework stack everytime an attachment is downloaded. E.g. when you display an image gallery with 40 thumbnails, that would generate 40 expensive Rails requests.

An alternative is to do store Paperclip attachments in the default public/system, but generate the path from a hash function, e.g. attachments/519b6d87b1225ba3/9/beach.jpg. The hash function hashes the containing record id, class name and a secret that must be unique to every application you deploy. This way attachments will only be accessible to users who know the link. However, you can never take authorization away (rarely an issue with images) and if you have directory indexes activated in Apache, you screwed up big time.

Copy the code below to config/initializers/paperclip_hashed_path.rb:

Paperclip.interpolates :hashed_path do |attachment, style| secret = 'CHANGE-THIS-FOR-EVERY-APPLICATION! jivEutIgtepsIvgikUpCoshBypMowjuegbisIgJo' hash = Digest::SHA512.hexdigest("--#{Rails.env}--#{}--#{}--#{secret}--") hash.slice(0, 16) end

Remember to change the secret in line 2 for every application you deploy! Changing a duplicated secret is very painful. We have a note on how to create strong secrets.

You can now use the hashed_path interpolation in your Paperclip attachments:

class Note < ActiveRecord::Base attachment_virtual_path = "/system/attachments/:rails_env/:hashed_path/:id/:style/:basename.:extension" attachment_real_path = ":rails_root/public" + attachment_virtual_path has_attached_file :attachment, :path => attachment_real_path, :url => attachment_virtual_path end

Paperclip will now automatically store attachments using hashed paths, and generate URLs accordingly. You don't need a protecting controller action here, because all security lies in the hashed secret.

To link to the protecting controller actions, e.g. in a view:

link_to 'Download attachment', [:attachment, @note]

After you deploy, make sure you do not get a directory index when you access

You can configure your apache server to expire file-URLs after some time, so they are only available as long as you want to provide it to the user. This gives some additional security to the not-that-incredibly-secure hashed paths storage approach. Read here for details.

  1. Further reading

Common mistakes when storing file uploads with Rails

Once an application no longer requires constant development, it needs periodic maintenance for stable and secure operation. makandra offers monthly maintenance contracts that let you focus on your business while we make sure the lights stay on.

Owner of this card:

Henning Koch
Last edit:
3 months ago
by Dominic Beger
About this deck:
We are makandra and do test-driven, agile Ruby on Rails software development.
License for source code
Posted by Henning Koch to makandra dev
This website uses short-lived cookies to improve usability.
Accept or learn more