Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Devise authentication for Rails' ActiveStorage
# Rails controller concern to enable Devise authentication for ActiveStorage.
# Put it in +app/controllers/concerns/blob_authenticatable.rb+ and include it when overriding
# +ActiveStorage::BlobsController+ and +ActiveStorage::RepresentationsController+.
#
# Optional configuration:
#
# Set the model that includes devise's database_authenticatable.
# Defaults to Devise.default_scope which defaults to the first
# devise role declared in your routes (usually :user)
#
# blob_authenticatable resource: :admin
#
# To specify how to determine if the current_user is allowed to access the
# blob, override the can_access_blob? method
#
# Minimal example:
#
# class ActiveStorage::BlobsController < ActiveStorage::BaseController
# include ActiveStorage::SetBlob
# include AdminOrUserAuthenticatable
#
# def show
# expires_in ActiveStorage::Blob.service.url_expires_in
# redirect_to @blob.service_url(disposition: params[:disposition])
# end
# end
#
# Complete example:
#
# class ActiveStorage::RepresentationsController < ActiveStorage::BaseController
# include ActiveStorage::SetBlob
# include AdminOrUserAuthenticatable
#
# blob_authenticatable resource: :admin
#
# def show
# expires_in ActiveStorage::Blob.service.url_expires_in
# redirect_to @blob.representation(params[:variation_key]).processed.service_url(disposition: params[:disposition])
# end
#
# private
#
# def can_access_blob?(current_user)
# @blob.attachments.map(&:record).all? { |record| record.user == current_user }
# end
# end
module BlobAuthenticatable
extend ActiveSupport::Concern
included do
around_action :wrap_in_authentication
end
module ClassMethods
def auth_resource
@auth_resource || Devise.default_scope
end
private
def blob_authenticatable(resource:)
@auth_resource = resource
end
end
private
def wrap_in_authentication
is_signed_in_and_authorized = send("#{self.class.auth_resource}_signed_in?") \
& can_access_blob?(send("current_#{self.class.auth_resource}"))
if is_signed_in_and_authorized
yield
else
head :unauthorized
end
end
def can_access_blob?(_user)
true
end
end
@jubilee2
Copy link

jubilee2 commented Mar 5, 2020

should we change & => && in line 71?

@martinstreicher
Copy link

martinstreicher commented Dec 28, 2020

If I create my own class ActiveStorage::BlobsController in my app/controllers folder, do I need to define a custom route to point to that controller?

@jubilee2
Copy link

jubilee2 commented Dec 29, 2020

If I create my own class ActiveStorage::BlobsController in my app/controllers folder, do I need to define a custom route to point to that controller?

I don't think so. it is override build-in controller so already have router to point this controller

@thiagomarinho
Copy link

thiagomarinho commented Jan 8, 2021

Hi, when using Rails 6.1 ActiveStorage::BlobsController becomes ActiveStorage::Blobs::RedirectController and ActiveStorage::RepresentationsController becomes ActiveStorage::Representations::RedirectController

@estani
Copy link

estani commented Aug 25, 2021

@thiagomainho awesome! I was already looking for the file when I found this entry. Thanks!

@astorrer
Copy link

astorrer commented Nov 18, 2021

Thank you for this. I think people underestimate how important it can be to protect file downloads. I went ahead and redirected my users to new_session_path(:user) rather than giving them a 401. Works well on Rails 6, but I wonder if this functionality shouldn't be included somewhere so we don't have to clutter up app/controllers.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment