master branch of Django REST framework.The docs explain how to use FileFields to serve media file URLs, but if these files are sensitive and need authentication/permissions, there is no mention of how to authenticate their download.
Django packages exist for this, such as django-downloadview, but it's not clear how (or if) the built in DRF authentication can interact with this. For example, these views don't interact with REST_FRAMEWORK 'DEFAULT_AUTHENTICATION_CLASSES'.
Does DRF have a solution for efficient media file serving that links into its authentication system? If so, I'd be happy to open a PR to clarify the docs.
I don't think DRF has anything built in for this – django-downloadview's efficiency bits seems like it wouldn't be too hard to hook up to DRF.
This sort of thing is what Nginx's X-Accel is for.
https://www.nginx.com/resources/wiki/start/topics/examples/x-accel/
Authenticate as normal in Django or DRF then have nginx serve the file.
DRF has no default for it for it is outside the scope of DRF.
django-downloadview looks promising but doesn't look like it supports DRF. It would probably be a good addition there.
Thank you for the responses.
The specific challenges I'm facing when I am trying to integrate django-downloadview (which supports Nginx X-Accel) and DRF is that django-downloadview does not integrate with DRF's 'DEFAULT_AUTHENTICATION_CLASSES'. So the views cannot take advantage of 'rest_framework.authentication.BasicAuthentication' or (third-party) 'rest_framework_simplejwt.authentication.JWTAuthentication' authentication middleware. This results in the django-downloadview View being presented the AnonymousUser user, and thus being rejected.
You could delegate to django-downloadview in your DRF viewset's response phase, so have DRF do everything else such as authentication and so on. Looks like BaseDownloadView could be easily extended for that...
I was able to patch everything together. In case this is useful to others, here is a minimal reproduction:
from typing import Any, List, Optional, Tuple, Type
from django.contrib.auth.models import User
from django.core.exceptions import PermissionDenied
from django.core.handlers.wsgi import WSGIRequest
from django_downloadview import DownloadResponse, ObjectDownloadView
from rest_framework.authentication import BaseAuthentication, BasicAuthentication
from rest_framework.permissions import BasePermission, DjangoObjectPermissions
from rest_framework_simplejwt.authentication import JWTAuthentication
class DRFAuthenticatedObjectDownloadView(ObjectDownloadView):
"""A generic file download view that automatically authenticates the user and
validates permissions using DRF middleware."""
permissions_class: Type[BasePermission] = DjangoObjectPermissions
# Note: This needs to be kept in sync with
# settings.py REST_FRAMEWORK DEFAULT_AUTHENTICATION_CLASSES
auth_classes: List[Type[BaseAuthentication]] = [BasicAuthentication, JWTAuthentication]
def authenticate(self, request: WSGIRequest) -> None:
"""Updates request.user if the client has sent headers that configured ``auth_classes``
successfully authenticate.
"""
for auth_class in self.auth_classes:
auth_resp: Optional[Tuple[User, None]] = auth_class().authenticate(request)
if auth_resp is not None:
request.user = auth_resp[0]
return
def has_permission(self, request: WSGIRequest) -> None:
"""Validate that the current User has appropriate access permissions to a Model.
Raises:
PermissionDenied: If the user does not have the required permissions.
"""
instance = self.get_object()
permissions = self.permissions_class()
if not (
permissions.has_permission(request, self)
and permissions.has_object_permission(request, self, instance)
):
raise PermissionDenied()
def get(self, request: WSGIRequest, *args: Any, **kwargs: Any) -> DownloadResponse:
"""Authenticate user and check permissions before returning the file download."""
self.authenticate(request)
self.has_permission(request)
return super().get(request, *args, **kwargs)
Nice example @johnthagen.
I'm going to close this as I think it's probably out of scope for DRF itself. Thanks.
This helped me a lot! Thanks a lot @johnthagen I am using django along with apache x-sendfile and have a complex permissions system to validate through before serving the file. Yet to implement but this looks very promising
Most helpful comment
I was able to patch everything together. In case this is useful to others, here is a minimal reproduction: