[フレーム]
Last Updated: December 31, 2020
·
12.21K
· cybersamx

Serving protected static content using nginx for speed and Rails server for authentication

Problem

A user needs to access a protected static content (eg. image) that must be served in https by a web server (for much faster download speed) only after the request has been successfully authenticated and authorized by a Rails server.

The Setup

Let's assume the following:

  • Nginx: nginx is running as a web and reverse proxy server. All requests to the rails server are passed onto port 3000 (ie. the default port for the Rails server)
  • Thin: rails server running on port 3000
  • Rails directory: the rails project root directory is located at /var/rails/rails_app
  • Directory where the protected static resources is called:/var/rails/rails_app/images_fs_dir
  • URL to access the protected static resources: http://myhost.com/images_uri_dir

Solution

Why use nginx to serve the static content?

Because nginx is 5-20x faster than thin when it comes to serving static content.

Logic flow:

  • Request is made to http://myhost.com/images_uri_dir/companyA/bar_chart.png
  • Request gets redirected to https://myhost.com/images_uri_dir/companyA/bar_chart.png (see rewrite ^ https://$host$request_uri permanent)
  • Nginx receives the request, matches the request path with it's mapping rules in nginx.conf, adds HTTP headers X-Accel-Redirect and X-Accel-Mapping, and sends the request to http://localhost:3000/images_uri_dir/companyA/bar_chart.png (see proxy_pass in nginx.conf)
  • Thin receives the request, looks up routes.rb and routes the request to ImagesController#show
  • The before_filter method in the controller performs the appropriate authentication and authorization
  • config/environments/production.rb tells the rails server to look for X-Accel-Redirect and X-Accel-Mapping in a request
  • Rails compares the file path that is passed to sendfile with what is defined in the left-hand-side of the X-Accel-Mapping value (ie. /var/rails/railsapp/=/imagesfsdir/). If the 2 values match, the controller passes the request back to nginx. Otherwise the controller will send the file back to the client
  • Nginx receives the request and looks at the right-hand-side of X-Accel-Mapping value (ie. /var/rails/railsapp/=/imagesfs_dir/). Since the request is internal to the host, nginx loads and sends the file back to the client (see internal in nginx.conf)

The Rails project root directory:

$ cd /var/rails/rails_app
$ ls
images_fs_dir
$ ls -R # List files recursively
.:
customerA customerB

./customerA
bar_chart.png
pie_chart.png

./customerB
bar_chart.png
pie_chart.png
...

nginx file should look like this:

events {
 worker_connections 1024;
}

http {
 include /etc/nginx/mime.types;
 index index.html index.htm;

 default_type application/octet-stream;

 upstream rails {
 server localhost:3000;
 }

 server {
 listen 80;
 server_name localhost;

 # Direct all http requests to https.
 location / {
 rewrite ^ https://$host$request_uri permanent;
 }
 }

 server {
 listen 443 default_server ssl;
 server_name localhost;
 root /var/rails/rails_app;

 ssl on;
 ssl_certificate /etc/ssl/certs/my_cert.crt;
 ssl_certificate_key /etc/ssl/keys/my_key.key;

 # Protected directory
 # Note: rails will handle /images_uri_dir requests
 location /images_fs_dir/ { 
 alias /var/rails/rails_app/; # Append the path with /
 internal; # Can't access this directory from direct access from the web
 }

 location / {
 proxy_redirect off;

 proxy_set_header Host $http_host;
 proxy_set_header X-Real-IP $remote_addr;
 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

 proxy_set_header X-Sendfile-Type X-Accel-Redirect;
 proxy_set_header X-Accel-Mapping /var/rails/rails_app/=/images_fs_dir/;

 # Make sure you open and close your X-Accel-Mapping value with /

 proxy_pass http://rails;
 }
 }
}

config/routes.rb should look like this:

# ...

get '/images_uri_dir/:customer_id/:category.png' => 'images#show'

#...

config/environments/production.rb should look like this:

# ...

# This redirects the request back to nginx to load and serve the static content after a success authentication/authorization

RailsApp::Application.configure do
 config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect'
 config.middleware.insert(0, Rack::Sendfile, config.action_dispatch.x_sendfile_header)

# ...

app/controllers/images_controller.rb

# ...

before_filter :validate_user

def validate_user
 # Authentication and authorization logic here
end

def show
 file_path = "#{Rails.root}/images_fs_dir/#{customer_id}/#{category}.png"

 send_file(file_path, type: 'image/png', disposition: 'inline')
end

def images_params
 params.permit(:customer_id, :category)
end

# ...

AltStyle によって変換されたページ (->オリジナル) /