Users can opt-in to enhanced security by enabling two-factor authentication. There is currently no enforcement of a policy, it is entirely optional. However, you could override this behaviour to enforce a custom policy.
For increased security views can be limited to two-factor-enabled users. This allows you to secure certain parts of the website. Doing so requires a decorator, class mixin or a custom inspection of a user's session.
You can use django-otp's built-in :meth:`~django_otp.decorators.otp_required` decorator to limit access to two-factor-enabled users:
from django_otp.decorators import otp_required
@otp_required
def my_view(request):
pass
The mixin :class:`~two_factor.views.mixins.OTPRequiredMixin` can be used to limit access to class-based views (CBVs):
class ExampleSecretView(OTPRequiredMixin, TemplateView):
template_name = 'secret.html'
The method is_verified() is added through django-otp's
:class:`~django_otp.middleware.OTPMiddleware` which can be used to check if the
user was logged in using two-factor authentication:
def my_view(request):
if request.user.is_verified():
# user logged in using two-factor
pass
else:
# user not logged in using two-factor
pass
Forcing users to enable two-factor authentication is not implemented. However, you could create your own custom policy.
By default the admin login is patched to use the login views provided by this application. Patching the admin is required as users would otherwise be able to circumvent OTP verification. See also :data:`~two_factor.TWO_FACTOR_PATCH_ADMIN`. Be aware that certain packages include their custom login views, for example django.contrib.admindocs. When using said packages, OTP verification can be circumvented. Thus however the normal admin login view is patched, OTP might not always be enforced on the admin views. See the next paragraph on how to do this.
In order to only allow verified users (enforce OTP) to access the admin pages, you have to use a custom admin site. You can either use :class:`~two_factor.admin.AdminSiteOTPRequired` or :class:`~two_factor.admin.AdminSiteOTPRequiredMixin`. See also the Django documentation on Hooking AdminSite instances into your URLconf.
If you want to enforce two factor authentication in the admin and use the
default admin site (e.g. because 3rd party packages register to
django.contrib.admin.site) you can monkey patch the default AdminSite
with this. In your urls.py:
from django.contrib import admin
from two_factor.admin import AdminSiteOTPRequired
admin.site.__class__ = AdminSiteOTPRequired
urlpatterns = [
path('admin/', admin.site.urls),
...
]
When a user was successfully verified using a OTP, the signal :data:`~two_factor.signals.user_verified` is sent. The signal includes the user, the device used and the request itself. You can use this signal for example to warn a user when one of his backup tokens was used:
from django.contrib.sites.shortcuts import get_current_site
from django.dispatch import receiver
from two_factor.signals import user_verified
@receiver(user_verified)
def test_receiver(request, user, device, **kwargs):
current_site = get_current_site(request)
if device.name == 'backup':
message = 'Hi %(username)s,\n\n'\
'You\'ve verified yourself using a backup device '\
'on %(site_name)s. If this wasn\'t you, your '\
'account might have been compromised. You need to '\
'change your password at once, check your backup '\
'phone numbers and generate new backup tokens.'\
% {'username': user.get_username(),
'site_name': current_site.name}
user.email_user(subject='Backup token used', message=message)
Users who only have a smartphone will have difficulty scanning the QR code
during setup. You can directly show the secret key within the QR code in text
form during setup by providing your own two_factor/core/setup.html template
and using the secret_key context variable.
To create a custom template, add a template called two_factor/_base.html.
As a bare minimum, its contents should contain a content block to load the
login forms into.
Some plugins, such as WebAuthn, have additional JavaScript that is necessary to
work properly. These are dynamically loaded in via the extra_media block
which should be located in the <head> tag:
<!DOCTYPE html>
<html>
<head>
<title>{% block title %}{% endblock %}</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="//cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.5.2/css/bootstrap.min.css" rel="stylesheet" media="screen">
<script defer src="//cdnjs.cloudflare.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script defer src="//cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.5.2/js/bootstrap.min.js"></script>
{% block extra_media %}{% endblock %}
</head>
<body>
{% block content_wrapper %}
<div class="container">
{% block content %}{% endblock %}
</div>
{% endblock %}
</body>
</html>
You can also use an existing template by extending it:
{% extends "your_app_name/base.html" %}