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 toImagesController#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
# ...
Written by Samuel Chow
Related protips
Have a fresh tip? Share with Coderwall community!
Post
Post a tip
Best
#Ruby
Authors
Sponsored by #native_company# — Learn More
#native_title#
#native_desc#