DEVELOPMENT by Pedro Resende

How to create a protected folder with basic auth

using Ruby on Rails

Post by Pedro Resende on the 29 of March of 2025 at 12:15

Tags: devdevelopmenttutorialrorrailsrubyRuby-On-Rails

header

This week, I've decided to investigate how to protect html files with basic auth on a Ruby on Rails app.

The problem is that if you store in the public/ folder it will be processed by the puma server, so, if you want to protect it, you need to use some sort of proxy.

Let's start by installing a clean Rails 8.0.2 environment for this example:

rails new protected_folder

Therefore, I've created a new folder called protected_folder under the storage folder.

cd protected_folder
mkdir -p storage/protected_folder

Let's install a new Gem called dotenv-rails to load the environment variables from the .env file.

bundle add dotenv-rails

Now, let's create a .env file under the root folder of the project, and add the following

BASIC_AUTH_USER=user
BASIC_AUTH_PASSWORD=password

Now, let's create a new controller called protected_folder under, app/controllers.

rails g controller protected_folder show

if you open the file, app/controllers/protected_folder_controller.rb, you should have the following

class ProtectedFolderController < ApplicationController
  def show
  end
end

Let's modify the controller to look like this

class ProtectedFolderController < ApplicationController
  before_action :authenticate
  protect_from_forgery except: :show

  def show
    requested_file = params[:path]

    # Convert to a Pathname object to sanitize the path
    requested_path = Pathname.new(requested_file).cleanpath.to_s
    # If no file extension is present, assume it's index.html file
    requested_path += "/index.html" unless requested_path.include?(".")

    # Ensure the path does not escape the intended directory
    safe_base = Rails.root.join("protected_storage").to_s
    file_path = Rails.root.join("protected_storage", requested_path).to_s

    # Ensure file_path is still inside protected_storage
    if file_path.start_with?(safe_base) == false
      return render plain: "401 Unauthorized", status: 401
    end

    if File.exist?(file_path) && !File.directory?(file_path)
      send_file file_path, disposition: "inline"
    else
      render plain: "File not found", status: :not_found
    end
  end

  private

  def authenticate
    authenticate_or_request_with_http_basic do |username, password|
      username == ENV["BASIC_AUTH_USER"] && password == ENV["BASIC_AUTH_PASSWORD"]
    end
  end
end

finally, open the files config/routes.rb and add the following

  get "/protected_folder/*path", to: "protected_folder#show", format: false

you can now add a new file to the storage/protected_folder folder, for example, a index.html file.

<h1>Protected Folder</h1>

Let's finally boot up our rails app by running

rails s

Open your browser on http://localhost:3000/protected_folder/index.html. You should have the following.

header

After providing the correct username and password defined in the .env file, you should see the content of the index.html file.

That is it, please let me know if you propose a different approach.