Overview and Key Concepts¶
The django_otp package contains a framework for processing one-time passwords as well as support for several types of OTP devices. Support for additional devices is handled by plugins, distributed separately.
Adding two-factor authentication to your Django site involves four main tasks:
Installing the django-otp plugins you want to use.
Adding one or more OTP-enabled login views.
Restricting access to all or portions of your site based on whether users have been verified by a registered OTP device.
Providing mechanisms to register OTP devices to user accounts (or relying on the Django admin interface).
Installation¶
Basic installation has only two steps:
Install
django_otp
and any plugins that you’d like to use. These are simply Django apps to be installed in the usual way.Add
django_otp.middleware.OTPMiddleware
toMIDDLEWARE
. It must be installed afterAuthenticationMiddleware
.
For example:
INSTALLED_APPS = [
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.sites',
'django.contrib.messages',
'django.contrib.staticfiles',
'django.contrib.admin',
'django.contrib.admindocs',
'django_otp',
'django_otp.plugins.otp_totp',
'django_otp.plugins.otp_hotp',
'django_otp.plugins.otp_static',
]
MIDDLEWARE = [
'django.middleware.common.CommonMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django_otp.middleware.OTPMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
]
The plugins contain models that must be migrated.
Upgrading¶
Version 0.2.4 of django-otp introduced a South migration to the otp_totp plugin. Version 0.3.0 added Django 1.7 and South migrations to all apps. Care must be taken when upgrading in certain cases.
The recommended procedure is:
Upgrade django-otp to 0.2.7, as described below.
Upgrade Django to 1.7 or later.
Upgrade django-otp to the latest version.
django-otp 0.4 dropped support for Django < 1.7.
Upgrading from 0.2.3 or Earlier¶
If you’re using django-otp <= 0.2.3, you need to convert otp_totp to South before going any further:
pip install 'django-otp==0.2.7'
python manage.py migrate otp_totp 0001 --fake
python manage.py migrate otp_totp
If you’re not using South, you can run python manage.py sql otp_totp
to see
the definition of the new last_t
field and then construct a suitable ALTER
TABLE
SQL statement for your database.
Upgrading to Django 1.7+¶
Once you’ve upgraded django-otp to version 0.2.4 or later (up to 0.2.7), it’s safe to switch to Django 1.7 or later. You should not have South installed at this point, so any old migrations will simply be ignored.
Once on Django 1.7+, it’s safe to upgrade django-otp to 0.3 or later. All plugins with models have Django migrations, which will be ignored if the tables have already been created.
If you’re already on django-otp 0.3 or later when you move to Django 1.7+ (see below), you’ll want to make sure Django knows that all migrations have already been run:
python manage.py migrate --fake <otp_plugin>
...
Upgrading to 0.3.x with South¶
If you want to upgrade django-otp to 0.3.x under South, you’ll need to convert all of the remaining plugins. First make sure you’re running South 1.0, as earlier versions will not find the migrations. Then convert any plugin that you have installed:
pip install 'django-otp>=0.3'
python manage.py migrate otp_hotp 0001 --fake
python manage.py migrate otp_static 0001 --fake
python manage.py migrate otp_yubikey 0001 --fake
python manage.py migrate otp_twilio 0001 --fake
Authentication and Verification¶
In a normal Django deployment, the user associated with a request is either authenticated or not. With the introduction of two-factor authentication, the situation becomes a little more complicated: while it is certainly possible to design a site such that two factors are required for any authentication, that’s only one option. It’s entirely reasonable to allow users to log in with either one or two factors and grant then access accordingly.
In this documentation, a user that has passed Django’s authentication API is called authenticated. A user that has additionally been accepted by a registered OTP device is called verified. On an OTP-enabled Django site, there are thus three levels of authentication:
anonymous
authenticated
authenticated + verified
When planning your site, you’ll want to consider whether different views will
require different levels of authentication. As a convenience, we provide the
decorator django_otp.decorators.otp_required()
, which is analogous to
login_required()
, but requires the user to
be both authenticated and verified.
OTPMiddleware
populates
request.user.otp_device
to the OTP device object that verified the current
user (if any). As a convenience, it also adds user.is_verified()
as a
counterpart to user.is_authenticated()
. It is not possible for a user to be
verified without also being authenticated. [1]
Plugins and Devices¶
A django-otp plugin is simply a Django app that contains one or more models that
are subclassed from django_otp.models.Device
. Each model class supports
a single type of OTP device. Remember that when we use the term device
in this context, we’re not necessarily referring to a physical device. At the
code level, a device is a model object that can verify a particular type of OTP.
For example, you might have a YubiKey that supports both the Yubico OTP
algorithm and the HOTP standard: these would be represented as different devices
and likely served by different plugins. A device that delivered HOTP values to a
user by SMS would be a third device defined by another plugin.
OTP plugins are distributed as Django apps; to install a plugin, just add it to
INSTALLED_APPS
like any other. The order can be significant: any time
we enumerate a user’s devices, such as when we ask the user which device they
would like to authenticate with, we will present them according to the order in
which the apps are installed.
OTP devices come in two general flavors: passive and interactive. A passive device is one that can accept a token from the user and verify it with no preparation. Examples include devices corresponding to dedicated hardware or smartphone apps that generate sequenced or time-based tokens. An interactive device needs to communicate something to the user before it can accept a token. Two common types are devices that use a challenge-response OTP algorithm and devices that deliver a token to the user through an independent channel, such as SMS.
Internally, device instances can be flagged as confirmed or unconfirmed. By default, devices are confirmed as soon as they are created, but a plugin or deployment that wishes to include a confirmation step can mark a device unconfirmed initially. Unconfirmed devices will be ignored by the high-level OTP APIs.
Built-in Plugins¶
django-otp includes support for several standard device types.
HOTPDevice
and
TOTPDevice
handle standard OTP
algorithms, which can be used with a variety of OTP generators. For example,
it’s easy to pair these devices with Google Authenticator using the otpauth
URL scheme. If you have either the segno or qrcode packages installed,
the admin interface will generate QR Codes for you.
HOTP Devices¶
HOTP is an algorithm that generates a pseudo-random sequence of codes based on an incrementing counter. Every time a prover generates a new code or a verifier verifies one, they increment their respective counters. This algorithm will fail if the prover generates too many codes without a successful verification.
If there is a failed attempt, this plugin will enforce an exponentially
increasing delay before allowing verification to succeed (see
OTP_HOTP_THROTTLE_FACTOR
). The
verify_token()
method automatically applies this
policy. For a better user experience, before calling
verify_token()
check whether verification is
disabled by calling the verify_is_allowed()
method.
- class django_otp.plugins.otp_hotp.models.HOTPDevice(*args, **kwargs)[source]¶
A generic HOTP
Device
. The model fields mostly correspond to the arguments todjango_otp.oath.hotp()
. They all have sensible defaults, including the key, which is randomly generated.- key¶
CharField: A hex-encoded secret key of up to 40 bytes. (Default: 20 random bytes)
- digits¶
PositiveSmallIntegerField: The number of digits to expect from the token generator (6 or 8). (Default: 6)
- tolerance¶
PositiveSmallIntegerField: The number of missed tokens to tolerate. (Default: 5)
- counter¶
BigIntegerField: The next counter value to expect. (Initial: 0)
- exception DoesNotExist¶
- exception MultipleObjectsReturned¶
- property bin_key¶
The secret key as a binary string.
- property config_url¶
A URL for configuring Google Authenticator or similar.
See https://github.com/google/google-authenticator/wiki/Key-Uri-Format. The issuer is taken from
OTP_HOTP_ISSUER
, if available.
- get_throttle_factor()[source]¶
This must be implemented to return the throttle factor.
The number of seconds required between verification attempts will be \(c2^{n-1}\) where c is this factor and n is the number of previous failures. A factor of 1 translates to delays of 1, 2, 4, 8, etc. seconds. A factor of 0 disables the throttling.
Normally this is just a wrapper for a plugin-specific setting like
OTP_EMAIL_THROTTLE_FACTOR
.
- class django_otp.plugins.otp_hotp.admin.HOTPDeviceAdmin(model, admin_site)[source]¶
ModelAdmin
forHOTPDevice
.
HOTP Settings¶
OTP_HOTP_ISSUER
Default: None
The issuer
parameter for the otpauth URL generated by
config_url
.
This can be a string or a callable to dynamically set the value.
OTP_HOTP_THROTTLE_FACTOR
Default: 1
This controls the rate of throttling. The sequence of 1, 2, 4, 8… seconds is
multiplied by this factor to define the delay imposed after 1, 2, 3, 4…
successive failures. Set to 0
to disable throttling completely.
TOTP Devices¶
TOTP is an algorithm that generates a pseudo-random sequence of codes based on the current time. A typical implementation will change codes every 30 seconds, although this is configurable. This algorithm will fail if the prover and verifier have clocks that drift too far apart.
If there is a failed attempt, this plugin will enforce an exponentially
increasing delay before allowing verification to succeed (see
OTP_TOTP_THROTTLE_FACTOR
). The
verify_token()
method automatically applies this
policy. For a better user experience, before calling
verify_token()
check whether verification is
disabled by calling the verify_is_allowed()
method.
- class django_otp.plugins.otp_totp.models.TOTPDevice(*args, **kwargs)[source]¶
A generic TOTP
Device
. The model fields mostly correspond to the arguments todjango_otp.oath.totp()
. They all have sensible defaults, including the key, which is randomly generated.- key¶
CharField: A hex-encoded secret key of up to 40 bytes. (Default: 20 random bytes)
- step¶
PositiveSmallIntegerField: The time step in seconds. (Default: 30)
- t0¶
BigIntegerField: The Unix time at which to begin counting steps. (Default: 0)
- digits¶
PositiveSmallIntegerField: The number of digits to expect in a token (6 or 8). (Default: 6)
- tolerance¶
PositiveSmallIntegerField: The number of time steps in the past or future to allow. For example, if this is 1, we’ll accept any of three tokens: the current one, the previous one, and the next one. (Default: 1)
- drift¶
SmallIntegerField: The number of time steps the prover is known to deviate from our clock. If
OTP_TOTP_SYNC
isTrue
, we’ll update this any time we match a token that is not the current one. (Default: 0)
- last_t¶
BigIntegerField: The time step of the last verified token. To avoid verifying the same token twice, this will be updated on each successful verification. Only tokens at a higher time step will be verified subsequently. (Default: -1)
- exception DoesNotExist¶
- exception MultipleObjectsReturned¶
- property bin_key¶
The secret key as a binary string.
- property config_url¶
A URL for configuring Google Authenticator or similar.
See https://github.com/google/google-authenticator/wiki/Key-Uri-Format. The issuer is taken from
OTP_TOTP_ISSUER
, if available. The image (for e.g. FreeOTP) is taken fromOTP_TOTP_IMAGE
, if available.
- get_throttle_factor()[source]¶
This must be implemented to return the throttle factor.
The number of seconds required between verification attempts will be \(c2^{n-1}\) where c is this factor and n is the number of previous failures. A factor of 1 translates to delays of 1, 2, 4, 8, etc. seconds. A factor of 0 disables the throttling.
Normally this is just a wrapper for a plugin-specific setting like
OTP_EMAIL_THROTTLE_FACTOR
.
- class django_otp.plugins.otp_totp.admin.TOTPDeviceAdmin(model, admin_site)[source]¶
ModelAdmin
forTOTPDevice
.
TOTP Settings¶
OTP_TOTP_ISSUER
Default: None
The issuer
parameter for the otpauth URL generated by
config_url
.
This can be a string or a callable to dynamically set the value.
OTP_TOTP_IMAGE
Default: None
The image
parameter for the otpauth URL generated by
config_url
.
It should be a HTTPS URL pointing to a square PNG image.
This will be read and displayed by some authenticator applications, e.g. FreeOTP.
This can be a string or a callable to dynamically set the value.
OTP_TOTP_SYNC
Default: True
If true, then TOTP devices will keep track of the difference between the
prover’s clock and our own. Any time a
TOTPDevice
matches a token in the
past or future, it will update
drift
to the number of
time steps that the two sides are out of sync. For subsequent tokens, we’ll
slide the window of acceptable tokens by this number.
OTP_TOTP_THROTTLE_FACTOR
Default: 1
This controls the rate of throttling. The sequence of 1, 2, 4, 8… seconds is
multiplied by this factor to define the delay imposed after 1, 2, 3, 4…
successive failures. Set to 0
to disable throttling completely.
Static Devices¶
- class django_otp.plugins.otp_static.models.StaticDevice(*args, **kwargs)[source]¶
A static
Device
simply consists of random tokens shared by the database and the user.These are frequently used as emergency tokens in case a user’s normal device is lost or unavailable. They can be consumed in any order; each token will be removed from the database as soon as it is used.
This model has no fields of its own, but serves as a container for
StaticToken
objects.- token_set¶
The RelatedManager for our tokens.
- exception DoesNotExist¶
- exception MultipleObjectsReturned¶
- get_throttle_factor()[source]¶
This must be implemented to return the throttle factor.
The number of seconds required between verification attempts will be \(c2^{n-1}\) where c is this factor and n is the number of previous failures. A factor of 1 translates to delays of 1, 2, 4, 8, etc. seconds. A factor of 0 disables the throttling.
Normally this is just a wrapper for a plugin-specific setting like
OTP_EMAIL_THROTTLE_FACTOR
.
- class django_otp.plugins.otp_static.models.StaticToken(*args, **kwargs)[source]¶
A single token belonging to a
StaticDevice
.- device¶
ForeignKey: A foreign key to
StaticDevice
.
- token¶
CharField: A random string up to 16 characters.
- exception DoesNotExist¶
- exception MultipleObjectsReturned¶
- class django_otp.plugins.otp_static.admin.StaticDeviceAdmin(model, admin_site)[source]¶
ModelAdmin
forStaticDevice
.
Static Settings¶
OTP_STATIC_THROTTLE_FACTOR
Default: 1
This controls the rate of throttling. The sequence of 1, 2, 4, 8… seconds is multiplied by this factor to define the delay imposed after 1, 2, 3, 4… successive failures. Set to 0 to disable throttling completely.
addstatictoken¶
The static plugin also includes a management command called addstatictoken
,
which will add a single static token to any account. This is useful for
bootstrapping and emergency access. Run manage.py addstatictoken -h
for
details.
Email Devices¶
- class django_otp.plugins.otp_email.models.EmailDevice(*args, **kwargs)[source]¶
A
SideChannelDevice
that delivers a token to the email address saved in this object or alternatively to the user’s registered email address (user.email
).The tokens are valid for
OTP_EMAIL_TOKEN_VALIDITY
seconds. Once a token has been accepted, it is no longer valid.Note that if you allow users to reset their passwords by email, this may provide little additional account security. It may still be useful for, e.g., requiring the user to re-verify their email address on new devices.
- email¶
EmailField: An alternative email address to send the tokens to.
- exception DoesNotExist¶
- exception MultipleObjectsReturned¶
- generate_challenge(extra_context=None)[source]¶
Generates a random token and emails it to the user.
- Parameters:
extra_context (dict) – Additional context variables for rendering the email template.
- get_cooldown_duration()[source]¶
Returns
OTP_EMAIL_COOLDOWN_DURATION
.
- get_throttle_factor()[source]¶
Returns
OTP_EMAIL_THROTTLE_FACTOR
.
- send_mail(body, **kwargs)[source]¶
A simple wrapper for
django.core.mail.send_mail()
.Subclasses (e.g. proxy models) may override this to customize delivery.
- class django_otp.plugins.otp_email.admin.EmailDeviceAdmin(model, admin_site)[source]¶
ModelAdmin
forEmailDevice
.
Email Settings¶
OTP_EMAIL_SENDER
Default: None
The email address to use as the sender when we deliver tokens. If not set, this
will automatically use DEFAULT_FROM_EMAIL
.
OTP_EMAIL_SUBJECT
Default: 'OTP token'
The subject of the email. You probably want to customize this.
OTP_EMAIL_BODY_TEMPLATE
Default: None
A raw template string to use for the email body. The render context will
include the generated token in the token
key. Additional template context
may be passed to
generate_challenge()
.
If this and OTP_EMAIL_BODY_TEMPLATE_PATH
are not set, we’ll render
the template ‘otp/email/token.txt’, which you’ll most likely want to override.
OTP_EMAIL_BODY_HTML_TEMPLATE
Default: None
A raw template string to use for the email html alternative body. The render context will
include the generated token in the token
key. Additional template context
may be passed to
generate_challenge()
.
If this and OTP_EMAIL_BODY_HTML_TEMPLATE_PATH
are not set, we won’t attach any
html alternative to the email.
OTP_EMAIL_BODY_TEMPLATE_PATH
Default: otp/email/token.txt
A path string to a template file to use for the email body. The render context
will include the generated token in the token
key. Additional template
context may be passed to
generate_challenge()
.
If this and OTP_EMAIL_BODY_TEMPLATE
are not set, we’ll render the
template ‘otp/email/token.txt’, which you’ll most likely want to override.
OTP_EMAIL_BODY_HTML_TEMPLATE_PATH
Default: None
A path string to a template file to use for the email html alternative body. The render context
will include the generated token in the token
key. Additional template
context may be passed to
generate_challenge()
.
If this and OTP_EMAIL_BODY_HTML_TEMPLATE
are not set, we won’t attach any html
alternative to the email.
OTP_EMAIL_TOKEN_VALIDITY
Default: 300
The maximum number of seconds a token is valid.
OTP_EMAIL_COOLDOWN_DURATION
Default: 60
This controls the cooldown period after a successful token generation. The token can be regenerated after the designated time period has fully elapsed. Set to 0 to disable cooldown completely.
OTP_EMAIL_THROTTLE_FACTOR
Default: 1
This controls the rate of throttling. The sequence of 1, 2, 4, 8… seconds is multiplied by this factor to define the delay imposed after 1, 2, 3, 4… successive failures. Set to 0 to disable throttling completely.
Other Plugins¶
The framework author also maintains a couple of other plugins for less common devices. Third-party plugins are not listed here.
django-otp-yubikey supports YubiKey USB devices.
django-otp-twilio supports delivering tokens via Twilio’s SMS service.
Settings¶
OTP_LOGIN_URL
Default: alias for LOGIN_URL
The URL where requests are redirected for two-factor authentication, especially
when using the otp_required()
decorator.
OTP_ADMIN_HIDE_SENSITIVE_DATA
Default: False
This controls showing some sensitive data on the Django admin site (e.g., keys and corresponding QR codes, static tokens). Note, it is respected by built-in plugins, but external ones may or may not support it.
Glossary¶
- authenticated¶
A user whose credentials have been accepted by Django’s authentication API is considered authenticated.
- device¶
A mechanism by which a user can acquire an OTP. This might correspond to a physical device dedicated to such a purpose, a virtual device such as a smart phone app, or even a set of stored single-use tokens.
- OTP¶
A one-time password. This is a generated value that a user can present as evidence of their identity. OTPs are only valid for a single use or, in some cases, for a strictly limited period of time.
- prover¶
An entity that is using an OTP to prove its identity. For example, a user who is providing an OTP token.
- token¶
An encoded OTP. Some OTPs consist of structured data, in which case they will be encoded into a printable string for transport.
- two-factor authentication¶
An authentication policy that requires a user to present two proofs of identity. The first is typically a password and the second is frequently tied to some physical device in the user’s possession.
- verified¶
A user whose credentials have been accepted by Django’s authentication API and also by a registered OTP device is considered verified.
- verifier¶
An entity that verifies tokens generated by a prover. For example, a web service that accepts OTPs as proof of identity.
Footnotes