Skip to content

Instantly share code, notes, and snippets.

@Schnouki
Last active February 10, 2021 02:39
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save Schnouki/32603c54bcd126fa86ea1fa15ac1b82a to your computer and use it in GitHub Desktop.
Save Schnouki/32603c54bcd126fa86ea1fa15ac1b82a to your computer and use it in GitHub Desktop.
WhiteNoiseMiddleware that restrics access to sourcemaps to authorized users
import fnmatch
from django.conf import settings
from django.http import HttpResponseForbidden
from whitenoise.middleware import WhiteNoiseMiddleware
class AuthenticatedWhiteNoiseMiddleware(WhiteNoiseMiddleware):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.auth_paths = getattr(settings, 'WHITENOISE_AUTHENTICATED_PATHS', None) or []
self.auth_cookie = getattr(settings, 'WHITENOISE_AUTH_COOKIE', None)
self.auth_cookie_domain = getattr(settings, 'WHITENOISE_AUTH_COOKIE_DOMAIN', None)
self.auth_cookie_secure = getattr(settings, 'WHITENOISE_AUTH_COOKIE_SECURE', None)
def __call__(self, request):
response = super().__call__(request)
response = self.process_response(request, response)
return response
def process_response(self, request, response):
if self.auth_cookie and hasattr(request, "user") and request.user.is_staff:
# User is authorized: add the auth cookie.
response.set_signed_cookie(self.auth_cookie, "1",
domain=self.auth_cookie_domain,
secure=self.auth_cookie_secure,
httponly=True)
return response
def serve(self, static_file, request):
if self.auth_cookie:
# Configured to enable authentication, let's do it!
path = request.path_info
auth_needed = any(fnmatch.fnmatch(path, pattern) for pattern in self.auth_paths)
if auth_needed and not request.get_signed_cookie(self.auth_cookie, default=False):
# Not authenticated even if needed: too bad…
return HttpResponseForbidden()
return super().serve(static_file, request)
WHITENOISE_AUTHENTICATED_PATHS = ["*.map"]
WHITENOISE_AUTH_COOKIE = "__static_auth"
WHITENOISE_AUTH_COOKIE_DOMAIN = ".example.com" # Required if statics for www.example.com are on static.example.com
WHITENOISE_AUTH_COOKIE_SECURE = not DEBUG
@Schnouki
Copy link
Author

Schnouki commented Dec 2, 2020

I just checked the project where I first added this middleware. It is definitely before AuthenticationMiddleware in the list, as it is actually a hack to avoid having to authenticate every request with Django. Instead, logging in (for another request) will set the auth cookie, which can then be used by this middleware without depending on Django's authentication.

Here's the thing: middlewares are processed from top to bottom when processing incoming requests, then from bottom to top when processing responses (see the schemas on this page). So here, the auth cookie is added when processing the response of a logged-in request. So if you login and browse any page on your site, the process_response() method of that middleware should be called, and it should set the auth cookie.

Then, new incoming requests (for static files) will have that cookie in the request, which will allow you to serve protected static files.

No idea about caching headers. My whole static file stuff (webpack + whitenoise) was setup to add hashes to filenames, so caching was not really an issue...

@omarsumadi
Copy link

Thank you so much for helping me through this from years ago to now. All this makes sense and this code should work in my project - I realize now the mistakes are something I need to correct from my own end.

Unfortunately, I implemented exactly what you are describing, but still I yet to be able to check if the request is authenticated in process_response without it raising an error or exception. Maybe this is because I am using ASGI in django?

I'm going to keep cracking at this, but I think you have given me all the information I need.

Thanks a million!

@omarsumadi
Copy link

omarsumadi commented Dec 2, 2020

django          |   File "/usr/local/lib/python3.8/site-packages/django/core/handlers/exception.py", line 34, in inner
django          |     response = get_response(request)
django          |   File "/app/edsproject/users/middleware.py", line 72, in __call__
django          |     response = self.process_response(request, response)
django          |   File "/app/edsproject/users/middleware.py", line 77, in process_response
django          |     print(request.user.is_authenticated)
django          | AttributeError: 'ASGIRequest' object has no attribute 'user'

Oh - I think perhaps I misinterpreted this whole project. Here's what I learned:

Initial Expectation:

  • On every static file request, we check if the user is authenticated. Because we are using cookies, we are somehow increasing performance, but I really didn't understand what is actually going on.

Actuality:

  • We go to the Login URL, we log in. (In the very beginning)
  • Response is eventually routed to process_response, and an authentication cookie is set as per your code. (this is now the main point of confusion, I need to make sure that my authentication is going to trigger the cookie being set - so is it any response by me from views is going to trigger the cookie setting? For instance, I authenticate my user in my views, is it going to route to process_response after the view is completed to add the cookie?).
  • Any subsequent times we are logged in and access a URL that leads to a view, the authentication cookies is added/refreshed via routing eventually to process_response when any response is being sent back from views.

On Static files

  • We placed Whitenoise middleware / now our custom middleware before authentication middleware's, so any requests that go through (as you said requests are propagated through middleware's linearly, not backwards like process_response) will NOT be present (getattr request user).
  • Because we are intending for Whitenoise to NOT hit authentication, and the fact that whitenoise will be first triggered before authentication middleware's, all requests for the user attribute is going to throw an exception/not be there.
  • However, because we set a cookie for authentication, we can just check that cookie instead without needing the authentication framework to be in place.
  • Then we check file paths with that cookie's status as signed or not acting as our weapon of authentication.

This explains why requests to URLs -> URLs had process_response working as normal, but for staticfiles, process_response was triggering the above error. But the above error is supposed to be there because we aren't checking authentication via Django but via cookies to bypass the authentication in total. Again, we placed the middleware before authentication thus any requests will NOT have authentication checking capabilities.

Sorry - I'm so new to all this i'm just having a hard time with the entire process of execution to get this working. Again, sorry for bothering you a lot about this - I'm happy to donate as well.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment