Authentication and Authorization

This section describes the process for verifying users against their registered OTP devices as well as limiting access based on this verification.

Authenticating Users

Soliciting an OTP token from a user is more complicated than soliciting a password. For one thing, each user may have any number of OTP devices registered to their account and the token itself won’t tell us which one is intended. And, of course, we won’t even know which devices we should check until after we’ve identified the user based on their username and password. Complicating this further is the fact some plugins are interactive, in which case verifying the user is at least a two-step process.

Verifying a user can happen in one or two stages. One option is to require an OTP up front along with a password. Alternatively, we can accept single-factor authentication initially, but allow (or require) the user to provide a second factor later on. The following sections begin with the simpler strategies and proceed to the lower-level APIs that will allow you to implement more complex policies.

The Easy Way

The Authentication Form

Django provides some high-level APIs to make it easy to authenticate users. If you’re accustomed to using Django’s built-in login view, this section will show you how to turn it into a two-factor login view.

In Django, user authentication actually takes place not in a view, but in an AuthenticationForm or a subclass. If you’re using Django’s built-in login view, you’re already using the default AuthenticationForm. This form performs authentication as part of its validation; validation only succeeds if the supplied credentials pass django.contrib.auth.authenticate().

If you want to require two-factor authentication in the default login view, the easiest way is to use django_otp.forms.OTPAuthenticationForm instead. This form includes additional fields and behavior to solicit an OTP token from the user and verify it against their registered devices. This form’s validation only succeeds if it is able to both authenticate the user with the username and password and also verify them with an OTP token. The form can be used with django.contrib.auth.views.login() simply by passing it in the authentication_form keyword parameter:

from django_otp.forms import OTPAuthenticationForm

urlpatterns = patterns('django.contrib.auth.views',
    url(r'^accounts/login/$', 'login', kwargs={'authentication_form': OTPAuthenticationForm}),
)

Following is a sample template snippet that’s designed for OTPAuthenticationForm:

<form action="." method="POST">
    <div class="form-row"> {{ form.username.errors }}{{ form.username.label_tag }}{{ form.username }} </div>
    <div class="form-row"> {{ form.password.errors }}{{ form.password.label_tag }}{{ form.password }} </div>
    {% if form.get_user %}
    <div class="form-row"> {{ form.otp_device.errors }}{{ form.otp_device.label_tag }}{{ form.otp_device }} </div>
    {% endif %}
    <div class="form-row"> {{ form.otp_token.errors }}{{ form.otp_token.label_tag }}{{ form.otp_token }} </div>
    <div class="submit-row">
        <input type="submit" value="Log in"/>
        {% if form.get_user %}<input type="submit" name="otp_challenge" value="Get Challenge" />{% endif %}
    </div>
</form>

The Admin Site

In addition to providing OTPAuthenticationForm for your normal login views, django-otp includes an AdminSite subclass for admin integration.

See the Django AdminSite documentation for more on installing custom admin sites. If you want to copy the default admin site into an OTPAdminSite, we find that the following works well. Note that it relies on a private property, so use this at your own risk:

otp_admin_site = OTPAdminSite(OTPAdminSite.name)
for model_cls, model_admin in admin.site._registry.iteritems():
    otp_admin_site.register(model_cls, model_admin.__class__)

The Token Form

If you already have an authenticated user and you just want to ask for an OTP token to verify, you can use django_otp.forms.OTPTokenForm.

The Low-Level API

Authorizing Users

If you design your site to always require OTP verification in order to log in, then your authorization policies don’t need to change. request.user.is_authenticated() will be effectively synonymous with request.user.is_verified(). If, on the other hand, you anticipate having both verified and unverified users on your site, you’re probably intending to limit access to some resources to verified users only. The primary tool for this is otp_required:

@django_otp.decorators.otp_required([redirect_field_name='next', login_url=None, if_configured=False])

Similar to login_required(), but requires the user to be verified. By default, this redirects users to OTP_LOGIN_URL.

Parameters:if_configured (bool) – If True, an authenticated user with no confirmed OTP devices will be allowed. Default is False.

If you need more fine-grained control over authorization decisions, you can use request.user.is_verified() to determine whether the user has been verified by an OTP device. if is_verified() is true, then request.user.otp_device will be set to the Device object that verified the user. This can be useful if you want to include the name of the verifying device in the UI.

If you want to use OTPs to establish trusted user agents (e.g. a browser that the user claims is on a private and secure computer), look at django-agent-trust and django-otp-agents.

Managing Devices

django-otp does not include any standard mechanism for managing a user’s devices outside of the admin interface. All plugins are expected to include admin integration, which should be sufficient for many sites. Some sites may want to provide users a self-service API to manage devices, but this will be very site-specific. Fortunately, managing a user’s devices is just a matter of managing Device-derived model objects, so it will be easy to implement. Be sure to note the warning about unsaved Device objects.