How to protect downloads but still have nginx serve the files
I’ve just been working on a project where a number of downloads needed to be restricted to specific users. I needed to authenticate the user and then allow them access to the file. This is not too difficult in rails:
def download
if authenticated?
send_file #{RAILS_ROOT}/downloads/images/myfile.zip'
end
end
The problem with this is that if the file is large, rails will spend a lot of time sending this file to the browser. The solution, hand it off to the webserver (in my case, nginx) to send the file once the authentication has succeeded. nginx supports a header named X-Accel-Redirect. Using this header, you send a full path to the file to be downloaded:
def download
if authenticated?
#Set the X-Accel-Redirect header with the path relative to the /downloads location in nginx
response.headers['X-Accel-Redirect'] = '/downloads/myfile.zip'
#Set the Content-Type header as nginx won't change it and Rails will send text/html
response.headers['Content-Type'] = 'application/octet-stream'
#If you want to force download, set the Content-Disposition header (which nginx won't change)
response.headers['Content-Disposition'] = 'attachment; filename=myfile.zip'
#Make sure we don't render anything
render :nothing => true
end
end
You will need to add a location directive in nginx marked as internal which nginx will use along with your path to get to the physical file.
location /downloads {
root /rails_deploy/current/downloads;
#Marked internal so that this location cannot be accessed directly.
internal;
}
Notes:
- You need to include the Content-Type header because nginx won’t change it and Rails sets it to text/html by default.
- The path you send in the X-Accel-Redirect header must be relative to a location directive within your nginx config.
You can also set additional control using the following headers:
X-Accel-Limit-Rate: 1024
X-Accel-Buffering: yes|no
X-Accel-Charset: utf-8
See the nginx documentation on X-Accel-Redirect for more information.