Sauvent Sauvent - 19 days ago 5
Apache Configuration Question

Django - Protecting Media files served with Apache with custom login_required decorator

I deployed a Django app using Apache, and I check for authentication in most views using a decorator.

@custom_decorator
def myView(request):
bla bla bla...


It's not the @login_required decorator that comes with Django, but it's almost the same thing, except that allows access only to users from certain groups. This works as intended.

Also, I'm serving media (user uploaded) files with Apache, like this:

Alias /media /path/to/media
<Directory /path/to/media>
Require all granted
</Directory


I can access the media files just fine, but the problem is that I can access them even if I'm not logged in, simply by typing the url manually, like:

mySite/media/myFile.png


Is there a way to limit access to the media files, hopefully using the custom decorator?

I stumbled across a similar question: How do you Require Login for Media Files in Django, but unfortunately the answer went way over my head.

Thanks in advance!




Solution:

Based on @MoinuddinQuadri answer and links, it seems that the easiest solution is to serve the files using a regular Django view, and apply the desired decorator, like this:

@custom_decorator
viewFile(request, objectID):
object = MyModel.object.get(id = objectID)
return HttpResponse(object.file, content_type = "image/png")


(In my case, I wanted to serve a FileField related to a Model, so in the view I pass the ID of the object instead of the file name).

Also, I commented out the corresponding code in the Apache conf:

### Alias /media /path/to/media
### <Directory /path/to/media>
### Require all granted
###</Directory


I had to change some templates to use the new view instead of the URL of the media file, but now it works as intended, locking out non-logged users.

However, this no longer uses Apache to serve the files, it uses Django itself, which according to the docs, is inneficient and not recommended.

Ideally you want to still serve the files using Apache and just use the view to protect its access, and for that you can use mod_xsendfile for Apache, or simply use Django Sendfile, which is a wrapper for the module just mentioned.

I tried the latter, but unfortunately it has problems with file names that have non-ascii characters. As my target are spanish-speaking users, I had to resort of just serving the files with Django, at least for now.

Thanks to @MoinuddinQuadri for the help!

Answer

When you mention media path to the apache, those files are served directly by Apache (or Nginx or any other web server). Those requests do not even goes through your Django application. Hence you do not have a control over those requests or the data served by them.

One way is to create your separate API to serve the static/media files. In that API, use the same validation that you do for other content.

Even better, if you have AWS (Amazon Web Services) or GCP (Google Cloud Platform) account, store the static files on the S3 or Cloud Storage respectively and serve their URL of files via your API.

PS: Do not forget to remove the media path from the Apache configuration. Else, Apache will keeps on serving those file.


Alternatively, as mentioned in Sarafeim's answer to Restricting access to private file downloads in Django which requires modification in both sever and application side. You need a way for your HTTP server to ask the application server if it is ok to serve a file to a specific user requesting it. You may achieve this using django-sendfile which uses the X-SendFile mechanism. As per the django-sendfile's README:

This is a wrapper around web-server specific methods for sending files to web clients. This is useful when Django needs to check permissions associated files, but does not want to serve the actual bytes of the file itself. i.e. as serving large files is not what Django is made for.

To understand more about the sendfile mechanism, read: Django - Understanding X-Sendfile