Join my Asyncio(Python) course on Udemy 👀

Hiding admin pages using Django middleware

May 20, 2021  ‐ 2 min read

You normally render Django templates from your views. However, in some cases you may want to short circuit something and not let a request get that far into your application.

The guys on the Django Chat Podcast made a case in a security episode for changing the Django admin URL prefix to anything other than /admin.

Of course this does not make the Django admin itself any more secure, however you exclude some script kiddies scanning the web for Django sites (by looking for the Django admin at /admin).

I have been taking a different "security through obscurity" approach for making the Django admin unavailable for others. Instead of changing the URL of the admin I raise a 404 in a custom middleware class to non-superuser when they try to access an admin route. In this case, superusers have to be logged in first via the website itself before they can access the admin pages.

This middleware class looks like the following:

from django.http import Http404


class AdminPagesMiddleware:
    def __init__(self, get_response):
        self.get_response = get_response

    def __call__(self, request):
        if request.path.startswith('/admin') and not request.user.is_superuser:
            raise Http404()
        return self.get_response(request)

We should not forget to register the middleware as well :).

MIDDLEWARE = [
    ...
    'project.middleware.auth_middleware.AdminPagesMiddleware',
    ...
]

And you definitely want tests for this middleware.

from django.urls import reverse
from django.test import TestCase, Client
from django.contrib.auth import get_user_model


class AdminPagesMiddlewareTestCase(TestCase):
    def test_throws_404_for_anonymous_users(self):
        r = self.client.get('/admin/', follow=True)
        assert r.status_code == 404

    def test_throws_404_for_users(self):
        get_user_model().objects.create_user(
            'ko', 'ko@test.com', 'pass', is_superuser=False)
        self.client.login(username='ko', password='pass')
        r = self.client.get('/admin/', follow=True)
        assert r.status_code == 404

    def test_accessible_for_superusers(self):
        get_user_model().objects.create_user(
            'ko', 'ko@test.com', 'pass', is_superuser=True)
        self.client.login(username='ko', password='pass')
        r = self.client.get('/admin/', follow=True)
        assert r.status_code == 200