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

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


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


Okay, so 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_decoratorviewFile(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.


I used solution #1 in the post "Django protected media files". There are two other solutions described here as well: "Unpredictable Urls" and "X-Sendfile", but the one I'm describing was my choice.

As @Sauvent mentioned, this causes the files to be served by Django and not by a web server (e.g. Apache). But it's quick and easy if you're not dealing with a lot of traffic or large files.

Basically, add the following to your urls.py:

@login_requireddef protected_serve(request, path, document_root=None, show_indexes=False):    return serve(request, path, document_root, show_indexes)urlpatterns = patterns('',    url(r'^{}(?P<path>.*)$'.format(settings.MEDIA_URL[1:]), protected_serve, {'document_root': settings.MEDIA_ROOT}),)

In my case I edited it to the following because my directories are set up differently and I use Login Required Middleware to ensure login is required everywhere (Django: How can I apply the login_required decorator to my entire site (excluding static media)?:

urlpatterns = patterns('',    url(r'^media/(?P<path>.*)$', "django.views.static.serve", {'document_root': settings.MEDIA_ROOT}),)