# 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

class django_otp.views.LoginView(**kwargs)

  This is a replacement for `django.contrib.auth.views.LoginView` that requires two-factor authentication. It’s slightly clever: if the user is already authenticated but not verified, it will only ask the user for their OTP token. If the user is anonymous or is already verified by an OTP device, it will use the full username/password/token form. In order to use this, you must supply a template that is compatible with both `OTPAuthenticationForm` and `OTPTokenForm`. This is a good view for `OTP_LOGIN_URL`.

### 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. If the verification fails, the `otp_verification_failed` signal is emitted. The form can be used with `django.contrib.auth.views.LoginView` simply by passing it in the `authentication_form` keyword parameter:

    from django.contrib.auth.views import LoginView
    from django_otp.forms import OTPAuthenticationForm

    urlpatterns = [
        url(r'^accounts/login/$', LoginView.as_view(authentication_form=OTPAuthenticationForm)),
    )

class django_otp.forms.OTPAuthenticationForm(request=None, *args, **kwargs)

  This form provides the one-stop OTP authentication solution. It should only be used when two-factor authentication is required: it does not have an OTP-optional mode. The form has four fields:

  > 1. `username` is inherited from `AuthenticationForm`.

  > 2. `password` is inherited from `AuthenticationForm`.

  > 3. `otp_device` uses a `Select` to allow the user to choose one of their registered devices. It will be empty as long as `form.get_user()` is `None` and should generally be omitted from the template in that case.

  > 4. `otp_token` is the field for entering an OTP token. It should always be included.

  In addition, if `form.get_user()` is not `None`, the template should include an additional submit button named `otp_challenge`. Pressing this button when `otp_device` is set to an interactive device will cause us to generate a challenge value for the user. Pressing the challenge button with a non-interactive device selected has no effect.

  The intended behavior of the form is as follows:

  > - Initially the `username`, `password`, and `otp_token` fields should be visible. Validation of `username` and `password` is the same as for `AuthenticationForm`. If we are able to authenticate the user based on username and password, then one of two things happens:

  >   > - If the user submitted an OTP token, we will enumerate all of the user’s OTP devices, asking each one to verify it in turn. If one of them succeeds, then authentication is fully successful and the user is logged in.

  >   > - If the user did not submit an OTP token or none of user’s devices accepted it, then a `ValidationError` is raised.

  > - In either case, as long as the user is authenticated by their password, `form.get_user()` will return the authenticated `User` object. From here on, this documentation assumes that username/password authentication succeeds on all subsequent submissions. If validation was not successful, then the form will be displayed again and this time the template should be sure to include the (now populated) `otp_device` field as well as the `otp_challenge` submit button.

  > - The user will then have to choose a specific device to authenticate against (or accept the default). If they press the `otp_challenge` button, we will ask that device to generate a challenge. The device will return a message for the user, which will be incorporated into the `ValidationError` message.

  > - If the user presses any other submit button, we will authenticate the username and password as always and then verify the OTP token against the chosen device. When that succeeds, authentication and verification are successful and the user is logged in.

  Error messages can be customized in subclasses; see `OTPAuthenticationFormMixin`.

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.

class django_otp.admin.OTPAdminSite(name='otpadmin')

  This is an `AdminSite` subclass that requires two-factor authentication. Only users that can be verified by a registered OTP device will be authorized for this admin site. Unverified users will be treated as if `is_staff` is `False`.

  has_permission(request)

    In addition to the default requirements, this only allows access to users who have been verified by a registered OTP device.

  login_form

    alias of `OTPAdminAuthenticationForm`

  login_template = 'otp/admin111/login.html'

    We automatically select a modified login template based on your Django version. If it doesn’t look right, your version may not be supported, in which case feel free to replace it.

  name = 'otpadmin'

    The default instance name of this admin site. You should instantiate this class as `OTPAdminSite(OTPAdminSite.name)` to make sure the admin templates render the correct URLs.

class django_otp.admin.OTPAdminAuthenticationForm(request=None, *args, **kwargs)

  An `AdminAuthenticationForm` subclass that solicits an OTP token. This has the same behavior as `OTPAuthenticationForm`.

Django has a mechanism for overriding-default-admin-site.

Note

If you switch to OTPAdminSite before setting up your first device, you’ll find yourself with a bit of a chicken-egg problem. Remember that you can always use the addstatictoken management command to bootstrap yourself in.

As a convenience, `OTPAdminSite` will override the admin login template. The template is a bit of a moving target, so this may get broken by new Django versions. Users will probably have a better and more consistent experience if you send them through your own login UI instead.

### 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`.

class django_otp.forms.OTPTokenForm(user, request=None, *args, **kwargs)

  A form that verifies an authenticated user. It looks very much like `OTPAuthenticationForm`, but without the username and password. The first argument must be an authenticated user; for an example of using this in a login view, see the source for `django_otp.views.LoginView`.

  This form will ask the user to choose one of their registered devices and enter an OTP token. Validation will succeed if the token is verified. See `OTPAuthenticationForm` for details on writing a compatible template (leaving out the username and password, of course).

  Error messages can be customized in subclasses; see `OTPAuthenticationFormMixin`.

  Parameters:

    - user (`User`) – An authenticated user.

    - request (`HttpRequest`) – The current request.

### Signals

django_otp.forms.otp_verification_failed

  This signal is sent when an OTP verification attempt fails. The source of the signal is `OTPAuthenticationFormMixin`, therefore it will generally be emitted only if using the provided `OTPAuthenticationForm` or `OTPTokenForm`. The signal provides the following arguments:

  `sender`

    The class of the form that attempted the verification.

  `user`

    The user that attempted the verification.

### Custom Forms

Most of the functionality of `OTPAuthenticationForm` and `OTPTokenForm` is implemented in a mixin class:

class django_otp.forms.OTPAuthenticationFormMixin

  Shared functionality for `AuthenticationForm` subclasses that wish to handle OTP tokens. Subclasses must do the following in order to use this:

  > 1. Define three additional form fields:

  >        otp_device = forms.CharField(required=False, widget=forms.Select)
  >        otp_token = forms.CharField(required=False, widget=forms.TextInput(attrs={'autocomplete': 'off'}))
  >        otp_challenge = forms.CharField(required=False)

  >    - `otp_device` will be a select widget with all of the user’s devices listed. Until the user has entered a valid username and password, this will be empty and may be omitted.

  >    - `otp_token` is where the user will enter their token.

  >    - `otp_challenge` is a placeholder field that captures an alternate submit button of the same name.

  > 2. Override `clean()` to call `clean_otp()` after invoking the inherited `clean()`. See `OTPAuthenticationForm` for an example.

  > 3. See `OTPAuthenticationForm` for information about writing a login template for this form. The file `django_otp/templates/otp/admin/login.html` is also a useful example.

  You will most likely be able to use `OTPAuthenticationForm`, `OTPAdminAuthenticationForm`, or `OTPTokenForm` directly. If these do not suit your needs–for instance if your primary authentication is not by password–they should serve as useful examples.

  This mixin defines some error messages in `OTPAuthenticationFormMixin.otp_error_messages`, which can be overridden in subclasses (refer to the source for message codes). For example:

      class CustomAuthForm(OTPAuthenticationFormMixin, AuthenticationForm):
          otp_error_messages = dict(OTPAuthenticationFormMixin.otp_error_messages,
              token_required=_('Please enter your authentication code.'),
              invalid_token=_('Incorrect authentication code. Please try again.'),
          )

### The Low-Level API

More customized integrations can use these APIs to manage the verification process directly.

Warning

Verifying OTP tokens should always take place inside of a transaction. If you’re loading the devices yourself, be sure to use `select_for_update()` to prevent concurrent access. Relevant APIs below have a `for_verify` parameter for this purpose.

django_otp.devices_for_user(user, confirmed=True, for_verify=False)

  Return an iterable of all devices registered to the given user.

  Returns an empty iterable for anonymous users.

  Parameters:

    - user (`User`) – standard or custom user object.

    - confirmed (bool) – If `None`, all matching devices are returned. Otherwise, this can be any true or false value to limit the query to confirmed or unconfirmed devices, respectively.

    - for_verify (bool) – If `True`, we’ll load the devices with `select_for_update()` to prevent concurrent verifications from succeeding. In which case, this must be called inside a transaction.

  Return type:

    iterable

django_otp.user_has_device(user, confirmed=True)

  Return `True` if the user has at least one device.

  Returns `False` for anonymous users.

  Parameters:

    - user (`User`) – standard or custom user object.

    - confirmed – If `None`, all matching devices are considered. Otherwise, this can be any true or false value to limit the query to confirmed or unconfirmed devices, respectively.

django_otp.verify_token(user, device_id, token)

  Attempts to verify a token against a specific device, identified by `persistent_id`.

  This wraps the verification process in a transaction to ensure that things like throttling polices are properly enforced.

  Parameters:

    - user (`User`) – The user supplying the token.

    - device_id (str) – A device’s persistent_id value.

    - token (str) – An OTP token to verify.

  Returns:

    The device that accepted `token`, if any.

  Return type:

    `Device` or `None`

django_otp.match_token(user, token)

  Attempts to verify a token on every device attached to the given user until one of them succeeds.

  Warning

  This originally existed for more convenient integration with the admin site. Its use is no longer recommended and it is not guaranteed to interact well with more recent features (such as throttling). Tokens should always be verified against specific devices.

  Parameters:

    - user (`User`) – The user supplying the token.

    - token (str) – An OTP token to verify.

  Returns:

    The device that accepted `token`, if any.

  Return type:

    `Device` or `None`

django_otp.login(request, device)

  Persist the given OTP device in the current session. The device will be rejected if it does not belong to `request.user`.

  This is called automatically any time `django.contrib.auth.login()` is called with a user having an `otp_device` atribute. If you use Django’s `LoginView` view with the django-otp authentication forms, then you won’t need to call this.

  Parameters:

    - request (`HttpRequest`) – The HTTP request

    - device (`Device`) – The OTP device used to verify the user.

class django_otp.models.Device(*args, **kwargs)

  Abstract base model for a device attached to a user. Plugins must subclass this to define their OTP models.

  Warning

  OTP devices are inherently stateful. For example, verifying a token is logically a mutating operation on the device, which may involve incrementing a counter or otherwise consuming a token. A device must be committed to the database before it can be used in any way.

  user

    ForeignKey: Foreign key to your user model, as configured by `AUTH_USER_MODEL` (`User` by default).

  name

    CharField: A human-readable name to help the user identify their devices.

  confirmed

    BooleanField: A boolean value that tells us whether this device has been confirmed as valid. It defaults to `True`, but subclasses or individual deployments can force it to `False` if they wish to create a device and then ask the user for confirmation. As a rule, built-in APIs that enumerate devices will only include those that are confirmed.

  objects

    A `DeviceManager`.

  classmethod from_persistent_id(persistent_id, for_verify=False)

    Loads a device from its persistent id:

        device == Device.from_persistent_id(device.persistent_id)

    Parameters:

      for_verify (bool) – If `True`, we’ll load the device with `select_for_update()` to prevent concurrent verifications from succeeding. In which case, this must be called inside a transaction.

  generate_challenge()

    Generates a challenge value that the user will need to produce a token.

    This method is permitted to have side effects, such as transmitting information to the user through some other channel (email or SMS, perhaps). And, of course, some devices may need to commit the challenge to the database.

    Returns:

      A message to the user. This should be a string that fits comfortably in the template `'OTP Challenge: {0}'`. This may return `None` if this device is not interactive.

    Return type:

      string or `None`

    Raises:

      Any `Exception` is permitted. Callers should trap `Exception` and report it to the user.

  generate_is_allowed()

    Checks whether it is permissible to call `generate_challenge()`.

    If it is allowed, returns `(True, None)`. Otherwise returns `(False, data_dict)`, where `data_dict` contains extra information, defined by the implementation.

    This method can be used to implement throttling of token generation for interactive devices. Client code should check this method before calling `generate_challenge()` and report problems to the user.

  is_interactive()

    Returns `True` if this is an interactive device.

    The default implementation returns `True` if `generate_challenge()` has been overridden, but subclasses are welcome to provide smarter implementations.

    Return type:

      bool

  property persistent_id

    A stable device identifier for forms and APIs.

  verify_is_allowed()

    Checks whether it is permissible to call `verify_token()`.

    If it is allowed, returns `(True, None)`. Otherwise returns `(False, data_dict)`, where `data_dict` contains extra information, defined by the implementation.

    This method can be used to implement throttling or locking, for example. Client code should check this method before calling `verify_token()` and report problems to the user.

    To report specific problems, the data dictionary can return include a `'reason'` member with a value from the constants in `VerifyNotAllowed`. Otherwise, an `'error_message'` member should be provided with an error message.

    `verify_token()` should also call this method and return False if verification is not allowed.

    Return type:

      (bool, dict or `None`)

  verify_token(token)

    Verifies a token.

    As a rule, the token should no longer be valid if this returns `True`.

    Parameters:

      token (str) – The OTP token provided by the user.

    Return type:

      bool

class django_otp.models.DeviceManager(*args, **kwargs)

  The `Manager` object installed as `Device.objects`.

  devices_for_user(user, confirmed=None)

    Returns a queryset for all devices of this class that belong to the given user.

    Parameters:

      - user (`User`) – The user.

      - confirmed – If `None`, all matching devices are returned. Otherwise, this can be any true or false value to limit the query to confirmed or unconfirmed devices, respectively.

class django_otp.models.GenerateNotAllowed(*values)

  Constants that may be returned in the `reason` member of the extra information dictionary returned by `generate_is_allowed()`.

  COOLDOWN_DURATION_PENDING

    Indicates that a token was generated recently and we’re waiting for the cooldown period to expire.

class django_otp.models.VerifyNotAllowed(*values)

  Constants that may be returned in the `reason` member of the extra information dictionary returned by `verify_is_allowed()`

  N_FAILED_ATTEMPTS

    Indicates that verification is disallowed because of `n` successive failed attempts. The data dictionary should include the value of `n` in member `failure_count`

## 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.