Robert Babaev
Posted on December 2, 2022
Contents
- Introduction
- MFA? What's That?
- Is This Gonna Be Super Complicated?
- Baseline Assumptions
- Installing Packages
- Pip
- Poetry
- Updating The Settings
- INSTALLED_APPS
- MIDDLEWARE
- ENABLE_MFA
- Updating The Admin Panel
- Updating The URLs
- Deployment
- Conclusion
Introduction
If you're like me, you've probably started up a Django project, got your Admin panel all nice and set up . . . and then realize down the road that maybe relying on just your password manager for account security isn't a great idea. We're all human, so let's fix that and add some MFA to our admin panel!
This task will use a TOTP-based method, leveraging apps such as Google Authenticator, Authy, etc. Since all MFA apps that work this way use essentially the same method, you don't need to worry about choosing one authenticator over the other, aside from possibly security considerations between the apps. Otherwise, pick your favourite and go!
MFA? What's That?
MFA (or Multi-Factor Authentication), is a security principle that revolves around combining authentication factors for added security. The three primary factor categories are Something You Know (think passwords, explicit information), Something You Have (an out-of-band device like a smartphone or hardware token), and Something You Are (biometrics, think fingerprints and retina scans). The theory behind this is that someone would have to compromise more than one factor in order to gain access to your account, as opposed to just cracking your password.
Since biometrics really aren't great for web authentication, we'll stick to the two other categories we have: Something You Know (username/password), and Something You Have (a smartphone).
Is This Gonna Be Super Complicated?
Nope! Thanks to libraries like django-otp
and qrcode
, implementing this in Django will be simple. You'll just have to flick a switch in the settings once you have MFA set up.
Baseline Assumptions
Let's assume that you already have:
- A Django application with some models already registered
- A superuser in the application
For the sake of the tutorial, let's say you have a project structure similar to this:
myproject/
├── data
│ ├── __init__.py
│ ├── admin.py
│ ├── apps.py
│ ├── migrations/
│ ├── models.py
│ ├── serializers.py
│ ├── tests.py
│ └── views.py
├── manage.py
└── myproject
├── asgi.py
├── __init__.py
├── settings.py
├── urls.py
└── wsgi.py
myproject
is the actual django project, and data
is an "app" in the project.
If you have Docker, Poetry, or whatever else you might have in your project, add that as needed.
Installing Packages
We'll need the following packages:
- django-otp
- qrcode
Pip
To install with Pip (assuming no requirements.txt file):
pip3 install django-otp qrcode
Poetry
To install with Poetry:
poetry add django-otp qrcode
Updating The Settings
We'll need to delve into the settings.py
file and update a few things.
INSTALLED_APPS
First, update your INSTALLED_APPS
list as follows, adding django_otp
and django_otp.plugins.otp_totp
:
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'data',
'django_otp',
'django_otp.plugins.otp_totp',
]
This will add the django-otp
capabilities into the app so we can actually work with it.
MIDDLEWARE
Next, update your middleware. You'll need to add django_otp.middleware.OTPMiddleware
somewhere after django.contrib.auth.middleware.AuthenticationMiddleware
, like this:
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django_otp.middleware.OTPMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
ENABLE_MFA
Finally, we'll need to add a setting to the bottom of the settings.py
file to enable or disable MFA (i.e. enable it once we have the token in our authenticator app).
I've used environment variables here (which, if you can't use cloud secrets, is probably the next best thing for security, as you're still not hardcoding anything into your application). Of course, if you're not using environment variables for some reason, adapt as necessary.
# Additional settings
ENABLE_MFA = os.environ.get("ENABLE_MFA", "false") == "true"
Updating The Admin Panel
Now, this method is optimized for a single application in the project, but you could do it for multiple with some tweaks.
First up, you'll need to change up how you define your admin panel.
For the sake of this tutorial, let's assume your old admin.py
file looked like this:
from django.contrib import admin
from data.models import *
# Register your models here.
class ArticleAdmin(admin.ModelAdmin):
pass
class ProjectAdmin(admin.ModelAdmin):
pass
class ContactAdmin(admin.ModelAdmin):
pass
admin.site.register(Article, ArticleAdmin)
admin.site.register(Project, ProjectAdmin)
admin.site.register(Contact, ContactAdmin)
Now, in order to effectively enable MFA we need to do a few things.
First, update your imports. Add the following to your imports section:
from django.conf import settings
from django_otp.admin import OTPAdminSite
from django.contrib.auth.models import User
from django_otp.plugins.otp_totp.models import TOTPDevice
from django_otp.plugins.otp_totp.admin import TOTPDeviceAdmin
Next, add the following code to the section just before you register the Article, Project, and Contact models:
class OTPAdmin(OTPAdminSite):
pass
if settings.ENABLE_MFA:
admin_site = OTPAdmin(name='OTPAdmin')
admin_site.register(User)
admin_site.register(TOTPDevice, TOTPDeviceAdmin)
else:
admin_site = admin.site
setup_admin_site = OTPAdmin(name='OTPAdmin')
setup_admin_site.register(User)
setup_admin_site.register(TOTPDevice, TOTPDeviceAdmin)
So what does all that junk do? Well, we have the OTPAdmin class, which is basically a wrapper for Django's actual admin panel. Now we do one of two things depending on whether MFA is enabled in the settings:
- If MFA is enabled, we make an OTPAdmin site and use that for our admin site, registering the User and TOTPDevice models. This allows us to login with our authenticator app.
- If it isn't, we use the Django default admin site, and set up a dummy admin site for the TOTP setup. This way, we can set up our MFA in the admin panel and then enable it with the flick of a switch in the settings.
Now, just update your model registrations:
admin_site.register(Article, ArticleAdmin)
admin_site.register(Project, ProjectAdmin)
admin_site.register(Contact, ContactAdmin)
In all, you should have:
from django.contrib import admin
from data.models import *
from django.conf import settings
from django_otp.admin import OTPAdminSite
from django.contrib.auth.models import User
from django_otp.plugins.otp_totp.models import TOTPDevice
from django_otp.plugins.otp_totp.admin import TOTPDeviceAdmin
# Register your models here.
class ArticleAdmin(admin.ModelAdmin):
pass
class ProjectAdmin(admin.ModelAdmin):
pass
class ContactAdmin(admin.ModelAdmin):
pass
class OTPAdmin(OTPAdminSite):
pass
if settings.ENABLE_MFA:
admin_site = OTPAdmin(name='OTPAdmin')
admin_site.register(User)
admin_site.register(TOTPDevice, TOTPDeviceAdmin)
else:
admin_site = admin.site
setup_admin_site = OTPAdmin(name='OTPAdmin')
setup_admin_site.register(User)
setup_admin_site.register(TOTPDevice, TOTPDeviceAdmin)
admin_site.register(Article, ArticleAdmin)
admin_site.register(Project, ProjectAdmin)
admin_site.register(Contact, ContactAdmin)
Updating The URLs
Finally, let's head over to our project's urls.py
file.
Add the following import:
from data.admin import admin_site
Remember that admin_site
variable we made that's either the OTPAdmin wrapper or the Django admin site? That's the one!
Then, swap the admin.site.urls
for admin_site.urls
in your urlpatterns
:
urlpatterns = [
path('admin/', admin_site.urls),
...
]
Now when we deploy, we'll either be met with the standard Django admin urls by default, or the OTPAdmin URLs when we enable MFA in the settings. Easy!
Deployment
Make and run your migrations and launch the app, making sure that ENABLE_MFA
is set to False (however you need to do that) in your settings.
Navigate to and you should see the old admin login:
Now you're going to want to do the following once you log in:
- Add a new TOTP Device (see the OTP_TOTP section of the admin panel).
- Select your user id (or search for it), and name the device however you like.
- Leave the rest of the settings as they are.
- Save the new device.
- Click on the "qrcode" link on the new device.
- Scan the QR Code into your authenticator app.
That's it! You should be able to set ENABLE_MFA to True in your settings, restart the server, and log in to your admin panel with your authenticator app.
Conclusion
This was a tutorial on how to integrate MFA into your Django admin panel. You can expand this to multiple apps in the project with some tweaks, but I'll leave that as an exercise for your situation.
Thank you for reading, and best of luck building secure apps!
Posted on December 2, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.