The Django admin interface is incredibly powerful, but this power entails a considerable responsibility. By default, Django gives you a full-featured backend to manage your models, but if left wide open, it’s also an attractive target for attackers. If you’re managing a production app, one of the best moves you can make is to secure Django admin with custom permissions and two-factor authentication (2FA). This adds layers of protection, ensuring that only the right users have access, and even then, only after proving who they are.
In this post, we’ll walk through the steps to secure your Django admin interface with:
- Custom roles and object-level permissions
- Two-factor authentication using
django-two-factor-auth - Extra hardening tips like route hiding and rate-limiting
Now, let’s jump in.
Why You Should Secure Django Admin?
Out of the box, Django admin exposes powerful actions like creating, editing, and deleting database records. If a malicious actor gains access, they can:
- Edit or delete critical content.
- Create unauthorized superuser accounts.
- Inject malicious code (via model fields or scripts).
That’s why taking a secure Django admin approach early in development, or during a security audit, is so important.
Step 1 – Create Custom Permissions and Roles
Django comes with a robust permissions framework you can build on. Here’s how to create role-based access using Group objects.
Creating User Groups and Assigning Permissions
Let’s say you want a “Content Editor” who can only modify blog posts.
from django.contrib.auth.models import Group, Permission
from django.contrib.contenttypes.models import ContentType
from blog.models import Post
# Create the group
editor_group = Group.objects.create(name='Content Editor')
# Assign permissions
content_type = ContentType.objects.get_for_model(Post)
change_post = Permission.objects.get(codename='change_post', content_type=content_type)
editor_group.permissions.add(change_post)
Then, assign a user to that group:
user = User.objects.get(username='jane')
user.groups.add(editor_group)
Now Jane can edit blog posts, but nothing else in the admin interface.
Go Further with Object-Level Permissions
Django’s default permissions are model-level. If you want to restrict access to specific objects, use django-guardian.
pip install django-guardian
Then update your settings:
INSTALLED_APPS += ['guardian']
AUTHENTICATION_BACKENDS = (
'django.contrib.auth.backends.ModelBackend', # default
'guardian.backends.ObjectPermissionBackend',
)
Assign object-level permissions:
from guardian.shortcuts import assign_perm
assign_perm('change_post', user, post_instance)
Now Jane can only edit that specific post.
Step 2 – Add Two-Factor Authentication (2FA)
Passwords aren’t enough anymore. That’s why adding 2FA is one of the smartest things you can do to secure Django admin.
We’ll use the excellent django-two-factor-auth package.
Install the Package
pip install django-two-factor-auth
Configure Settings
Add the required apps to your INSTALLED_APPS:
INSTALLED_APPS += [
'django_otp',
'django_otp.plugins.otp_static',
'django_otp.plugins.otp_totp',
'two_factor',
]
Update your urls.py:
from two_factor.urls import urlpatterns as tf_urls
urlpatterns = [
path('', include(tf_urls)),
path('admin/', admin.site.urls),
]
Make Admin Require 2FA
You can create a custom admin site that enforces 2FA using TwoFactorAdminSite:
from two_factor.admin import AdminSiteOTPRequired
from django.contrib import admin
class SecureAdminSite(AdminSiteOTPRequired):
pass
secure_admin_site = SecureAdminSite(name='SecureAdmin')
Use this site instead of the default admin.site.
Extra Hardening Tips to Secure Django Admin
Once you’ve set up roles and 2FA, there’s still more you can do to tighten the screws. These extra hardening tips will help you further secure Django admin by reducing its visibility and blocking common attack vectors.
Move Admin to a Non-Standard URL
Bots and scanners know /admin/ by heart. Changing the route can reduce brute-force attempts.
# urls.py
path('secure-admin-9834/', admin.site.urls),
Rate Limit Login Attempts
Install django-axes to prevent brute-force attacks.
pip install django-axes
INSTALLED_APPS += ['axes']
MIDDLEWARE += ['axes.middleware.AxesMiddleware']
It tracks failed login attempts and locks out abusive IPs automatically.
Restrict Admin Access by IP
Here’s a simple middleware to allow access to admin only from specific IP addresses:
# middleware.py
from django.http import HttpResponseForbidden
class AdminIPRestrictionMiddleware:
ALLOWED_IPS = ['127.0.0.1', '192.168.1.10']
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
if request.path.startswith('/secure-admin-9834/') and request.META['REMOTE_ADDR'] not in self.ALLOWED_IPS:
return HttpResponseForbidden("Access Denied")
return self.get_response(request)
Add it to MIDDLEWARE.
Final Thoughts
Custom permissions and two-factor authentication create multiple barriers between attackers and your admin interface. By combining custom permissions, object-level access, and two-factor authentication, you’ll drastically improve your ability to secure Django admin from unauthorized access and abuse. Upgrade your setup today and sleep easier knowing your admin site isn’t a backdoor waiting to happen.
Want to go further? Audit admin users regularly, enable HTTPS across your site, and automate security testing as part of your deployment workflow.
If you have any questions or thoughts on securing Django admin, feel free to share them in the comments.