Two Factor Authentication in Django
Motivation
2FA (two-factor authentication) is a security process that requires users to provide two forms of identification to access an online account or platform. The two factors can be something the user knows (like a password or PIN) and something the user has (like a mobile device or security token), or something the user is (like biometric data).
I wanted to learn how to setup 2FA in Django and try it out for myself. In this article, I show you how to setup a basic 2FA system using the Django Two Factor Authentication package. At the end, a user can choose to enable 2FA and then use an app such as Google Authenticator to enter generated authentication tokens.
Setup
This is largely taken from the documentation.
Install the package with pip.
pip install django-two-factor-auth
It also needs the
phonenumbers
library so you have to install that as well.pip install django-two-factor-auth[phonenumbers]
Add the following apps to the
INSTALLED_APPS
:
INSTALLED_APPS = [ ... 'django_otp', 'django_otp.plugins.otp_static', 'django_otp.plugins.otp_totp', 'django_otp.plugins.otp_email', # <- if you want email capability. 'two_factor', ]
There are more options. For example, you can set this up to use a yubikey or to send SMS login PINs to a mobile device. Enabling SMS notification requires Twilio or something similar. That is beyond the scope of this article.
- Add the following entries to the
MIDDLEWARE
setting.
MIDDLEWARE = ( ... 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django_otp.middleware.OTPMiddleware', ... )
- Add the following to
settings.py
:
# This is the URL to go to when the user needs to login. LOGIN_URL = 'two_factor:login' # this one is optional. # This is where they are directed after authentication. # It does not have to be to their 2FA profile. LOGIN_REDIRECT_URL = 'two_factor:profile'
The profile page allows the user to see their recovery tokens as well as disable 2FA if they so choose.
- Register the urls in
urls.py
for the core app.
from two_factor.urls import urlpatterns as tf_urls urlpatterns = [ path('', include(tf_urls)), ... ]
That is pretty much it! One thing to be aware of - it is easy for users to circumvent 2FA if you have the urls for another package such as allauth
registered as well. You can still use allauth
with 2FA as far as I can understand, you just have to be careful with which URLs you allow. Now, when the user sets up 2FA, they will get a QR code to use for a third-party app like Google Authenticator to generate login tokens.
You can even use a decorator to require certain views be accessible only if the user is aunthenticated with 2FA. (According to the documentation, there is no way to force a user to use 2FA with this package.)
from django_otp.decorators import otp_required @otp_required def my_view(request): pass
That is pretty much it! You can override the basic styling and implementation of the provided templates just like with any other Django template. But that is for another day.