Django + NGINX Protecting Media Links using X-Accel-redirect not working

1.2k Views Asked by At

I am trying to get the image links delivered via Django Views to undergo additional security checks so that only the person with the correct permission can access the link.

Currently, this is the setup: an User X uploads a photo from his account. The files get saved in media/images/ folder. And the django CreateView redirects to 'view/<id_of_image_in_db>' --> Which triggers Django Detailview function .

    class PhotosHDView(DetailView):
    model = MYPhotos
    template_name = 'detail.html'
    context_object_name  = 'photos'

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['userName'] = self.request.user
        return context

The detail.html gets the photos object and renders in card.

    {% block content %}
    <div class="container mx-auto">
        <div class="card ">
            <img src="{{photos.image.url}}" class="card-img-top" alt="...">
        </div>
     </div>
    {% endblock %}

When the page is rendered "{{photos.image.url}}" will be /media/images/<image_name>

This all works fine in production with NGINX with the default method where the images are served by NGINX without communicating with Django server.

Working NGINX Config:

    location /media/ {
        autoindex off;
        alias /home/ubuntu/photoApp/media/;
    }

Now if you right-click the image and get the image URL rendered in detail view and go to an incog window and paste the link, the image will be displayed irrespective of who is accessing it. Because there is no communication from NGINX with django whether proper permission exists.

Tried Solutions:

Based on many answers, I tried using X-Accel-redirect and been failing miserably to get it to work.

**Added in urls.py**
re_path(r'^media/$',login_required(views.sid), name='SecureImages' ),

** sid to function when the path is true **
def sid(request):
    print(' Triggered SID FUNCTION', request)
    response = HttpResponse()
    # Showing cat image irrespective of image requested--> Simple Test
    # Once it works, get the image requested from request and send the redirect
    response['X-Accel-Redirect'] ='media/images/cat.jpeg'
    return request

** NGINX Config Changes **
    location /media/ {
        autoindex off;
        internal; #<-- Added this
        alias /home/ubuntu/photoApp/media/;
    }

After these changes, none of the images are shown even when accessed by the proper User.

What am I doing wrong ?? Please suggest a solution for this.

Sample GitHub project is here: https://github.com/IamVNIE/django-photoapp-project

1

There are 1 best solutions below

0
The FPGA Race On

Finally Got it to Work..

1 - Make All the views that render images to include ID tag so that it becomes easier to lookup for access

class PhotosHDView(DetailView):
    model = MYPhotos
    template_name = 'detail.html'
    context_object_name  = 'photos'

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        x = context[self.context_object_name]
        context['safeURLs'] = x.image.url.replace('/images','/images/'+str(x.id))
        context['userName'] = self.request.user
        return context

2 - Urls.py Modification - So that all /media/* urls go through django and not nginx

url(r'^media/(?P<path>.*)', login_required(views.media_access), name='media'),

3 - In views.media_access check for proper access

def media_access(request, path):
    #--> Check For Access Code
    if access_granted:
        response = HttpResponse()
        del response['Content-Type']
        response['X-Accel-Redirect'] = '/protected/' + path.replace(str(id)+'/','')
                #/protected/ will go through Nginx
                # Remove the ID that we tagged while rendering the object
        print('Final Response', response['X-Accel-Redirect'])
        return response
    else:
        return HttpResponseForbidden('Not authorized to access this media.')

4 - Final nail in the coffin - NGINX Update

    location ^~ /protected/ {
        internal;
        alias /home/ubuntu/photoApp/media/;
    }

        location / {
        # Other Proxy Stuff
        proxy_buffering off;
        }


Basically made all /media/ requests to go django --> Checks Access --> django tags /media/ as /protected/ --> NGINX picks up /protected/ and serves the file from media folder.

The only drawback is all the ListView and DetailView have to be tagged with object ID and later on stripped away.

I felt without this tag, we would have to make query to search the DB for the file name to lookup proper owner, which would make the system inefficient.

Let me know if this is the correct way.

I have updated the project in github with working code: https://github.com/IamVNIE/django-photoapp-project