Quickly add 2FA (email) for your custom Django admin

Keeping your Django admin panel safe is crucial. Regular passwords are good, but adding another layer of security is even better. That's where Two-Factor Authentication (2FA) comes in.

Just for clarity, I will repeat why 2FA is so important, even if it's a bit more inconvenient. If someone guesses/steals your password (the first lock), they still can't get in without the code from your authenticator app / email / hardware key (the second lock). This makes it much harder for attackers to break into your admin panel.

This post is the fastest guide for adding 2FA (with email) to your Django admin - especially if you're using a custom admin interface and the instructions in other blog posts are not working. I prefer email, and not an authenticator app since it's way easier to get started without requiring any additional new equipment/app.

Get started

We will use the battle-tested django-otp project. The project supports many types of authenticators (such as authenticator apps, hardware keys, etc) so make sure to check out the documentation to add additional methods of authenticating if needed.

pip install django-otp

Installation

Head to your settings.py and make the following modifications:

INSTALLED_APPS = [
    # [...]
    "django_otp",
    "django_otp.plugins.otp_static",
    "django_otp.plugins.otp_email",
]

MIDDLEWARE = [
    # [...]
    "django.contrib.auth.middleware.AuthenticationMiddleware",
    # BELOW AuthenticationMiddleware!
    "django_otp.middleware.OTPMiddleware",

settings.py

Now somewhere, probably in admin.py you are defining your custom admin panel. Replace the base class with the OTP one (for the login screen to be replaced automatically with the one asking for the 2FA).

# REPLACE THIS
class CustomAdminSite(AdminSite):
    # [...]

# WITH THIS
class CustomAdminSite(OTPAdminSite):
    # [...]

admin_site = CustomAdminSite(name="myadmin")

admin.py

The final step, in your urls.py register the model for registering email as 2FAs for your users. Important to note here, that this registration should not be in the same file that you are defining the custom admin class since it will create circular dependencies.

# Placed here (and not in custom_admin/admin.py) to avoid circular import
admin_site.register(EmailDevice, EmailDeviceAdmin)

urls.py

Now run the migrations and you are ready to use it.

python manage.py migrate

Please note that the emails that will be sent, will be using Django's email configuration.

Usage

When you go to your admin site, you will be greeted with 3 input fields: username/email, password, and OTP. To get access to actually set up 2FA for your users, you need a one-time password. This is what the opt_static app you installed above is doing. To generate a static one-time code run the following command (remember, you can only use once this code):

python manage.py addstatictoken my_username

Use this to log in, and head to EmailOTP model to set up the 2FA email for each user. Congratulations, your admin site is now protected with an additional authentication layer :)

Happy coding!