There are two things you are asking:
- How to prevent external access to a (set of) file(s)
- How to authenticate a user and allow PHP (and the user) access to the file(s)
Hopefully this clarifies the role each component plays - although it may be a bit excessive.
Prevent External Access to files:
This part is done by your web server - in this case nginx.
In your nginx configuration (your server block), you specify a root
path. By default all files under this root path are directly accessible.
For instance, consider the following (the domain is 'example.com')
root /var/www/example.com/public_html
If you have a file, uploaded_file.zip
, it will be accessible in the following ways:
- Location: /var/www/example.com/public_html/uploaded_file.zip - accessible via example.com/uploaded_file.zip
- Location: /var/www/example.com/uploaded_file.zip - not accessible (even using example.com/../uploaded_file.zip)
In essence, a file above the document root, will not be accessible via a browser, however, most configurations of PHP will allow your code to read the file, and deliver it (restrictions on this are often a result of open_basedir
)
In some scenarios, it may be preferable to have your files under the document root. To do this and still prevent (direct) external access, you will use the internal
directive. For instance:
location /uploads {
internal;
}
Now, any files placed in /var/www/example.com/public_html/uploads are only internally accessible (i.e. cannot be accessed via a browser, but can be accessed by your PHP scripts). If desired, you can also setup an alias on the /uploads location, so that another path can also be used to reference it.
At this point, your files are not directly accessible, but your scripts can still be able to serve them.
User Authentication and PHP:
Consider the following basic design:
A user can access a file directly, or can go the login page. If they try to access a file directly and are logged in (and have permission), the file will download, otherwise, they will end up on the login screen. To upload a file, a user needs to be logged in.
Let's say that your download script (the page that will deliver the file to the user) is called 'download.php'. A typical URL may be example.com/download.php?file=uploaded_file.zip. It is quite likely that you don't want your URLs to include the php filename. To avoid this, you can setup a rewrite rule in your nginx config that will point another location (let's say /files) at your script. Add to your nginx config:
rewrite ^/files/(.*) /download.php?file=$1 last;
(Which amounts to changing the requested path of any requests starting with /files, and capturing everything after the / and passing it as a query parameter to your php file);
Now, your file can be accessed via: example.com/files/uploaded_file.zip (which internally redirects to your php script).
Your PHP file (download.php) now does the following:
- Start the session
- If the user is logged in, proceed, otherwise, redirect to a login page, passing the target file as part of the query string
- Query your database (e.g. MySQL) to see if the user can access this file
- If the user has permission, proceed, otherwise, redirect to an error page
Output the necessary headers (Content-Type, Content-Disposition, etc) and set:
X-Accel-Redirect: /uploads/uploaded_file.zip;
Note, you can do the actual download fully from PHP (e.g. using readfile
)- it is just a bit more efficient, especially for large files) to do it via nginx. Also, despite the user appearing to access the file directly, internally, it is going through PHP, which is authenticating them before allowing them to continue.
URL -> nginx (redirect to PHP) -> PHP (authenticate) -> nginx (serve file)
On the uploading side of things, you will move_uploaded_file
to /var/www/example.com/public_html/uploads (which is protected by the 'internal' directive, and so, files cannot be directly accessed), and save permissions to your database.