The HMAC activation workflow

The HMAC workflow, found in registration.backends.hmac, implements a two-step registration process (signup, followed by activation), but unlike the older model-based activation workflow uses no models and does not store its activation key; instead, the activation key sent to the user is a timestamped, HMAC-verified value.

Unless you need to maintain compatibility in an existing install of django-registration which used the model-based workflow, it’s recommended you use the HMAC activation workflow for two-step signup processes.

Behavior and configuration

Since this workflow does not make use of any additional models beyond the user model (either Django’s default django.contrib.auth.models.User, or a custom user model), do not add registration to your INSTALLED_APPS setting.

You will need to configure URLs, however. A default URLconf is provided, which you can include() in your URL configuration; that URLconf is registration.backends.hmac.urls. For example, to place user registration under the URL prefix /accounts/, you could place the following in your root URLconf:

from django.conf.urls import include, url

urlpatterns = [
    # Other URL patterns ...
    url(r'^accounts/', include('registration.backends.hmac.urls')),
    # More URL patterns ...
]

That URLconf also sets up the views from django.contrib.auth (login, logout, password reset, etc.), though if you want those views at a different location, you can include() the URLconf registration.auth_urls to place only the django.contrib.auth views at a specific location in your URL hierarchy.

Note

URL patterns for activation

Although the actual value used in the activation key is the new user account’s username, the URL pattern for ActivationView does not need to match all possible legal characters in a username. The activation key that will be sent to the user (and thus matched in the URL) is produced by django.core.signing.dumps(), which base64-encodes its output. Thus, the only characters this pattern needs to match are those from the URL-safe base64 alphabet, plus the colon (“:”) which is used as a separator.

The default URL pattern for the activation view in registration.backends.hmac.urls handles this for you.

This workflow makes use of up to three settings:

By default, this workflow uses registration.forms.RegistrationForm as its form class for user registration; this can be overridden by passing the keyword argument form_class to the registration view.

Views

Two views are provided to implement the signup/activation process. These subclass the base views of django-registration, so anything that can be overridden/customized there can equally be overridden/customized here. There are some additional customization points specific to the HMAC implementation, which are listed below.

For an overview of the templates used by these views (other than those specified below), and their context variables, see the quick start guide.

class registration.backends.hmac.views.RegistrationView

A subclass of registration.views.RegistrationView implementing the signup portion of this workflow.

Important customization points unique to this class are:

create_inactive_user(form)

Creates and returns an inactive user account, and calls send_activation_email() to send the email with the activation key. The argument form is a valid registration form instance passed from register().

get_activation_key(user)

Given an instance of the user model, generates and returns an activation key (a string) for that user account.

get_email_context(activation_key)

Returns a dictionary of values to be used as template context when generating the activation email.

send_activation_email(user)

Given an inactive user account, generates and sends the activation email for that account.

email_body_template

A string specifying the template to use for the body of the activation email. Default is "registration/activation_email.txt".

email_subject_template

A string specifying the template to use for the subject of the activation email. Default is "registration/activation_email_subject.txt". Note that, to avoid header-injection vulnerabilities, the result of rendering this template will be forced into a single line of text, stripping newline characters.

class registration.backends.hmac.views.ActivationView

A subclass of registration.views.ActivationView implementing the activation portion of this workflow.

Important customization points unique to this class are:

get_user(username)

Given a username (determined by the activation key), look up and return the corresponding instance of the user model. Returns None if no such instance exists. In the base implementation, will include is_active=False in the query to avoid re-activation of already-active accounts.

validate_key(activation_key)

Given the activation key, verifies that it carries a valid signature and a timestamp no older than the number of days specified in the setting ACCOUNT_ACTIVATION_DAYS, and returns the username from the activation key. Returns None if the activation key has an invalid signature or if the timestamp is too old.

How it works

When a user signs up, the HMAC workflow creates a new User instance to represent the account, and sets the is_active field to False. It then sends an email to the address provided during signup, containing a link to activate the account. When the user clicks the link, the activation view sets is_active to True, after which the user can log in.

The activation key is simply the username of the new account, signed using Django’s cryptographic signing tools (specifically, signing.dumps() is used, to produce a guaranteed-URL-safe value). The activation process includes verification of the signature prior to activation, as well as verifying that the user is activating within the permitted window (as specified in the setting ACCOUNT_ACTIVATION_DAYS, mentioned above), through use of Django’s TimestampSigner.

Comparison to the model-activation workflow

The primary advantage of the HMAC activation workflow is that it requires no persistent storage of the activation key. However, this means there is no longer an automated way to differentiate accounts which have been purposefully deactivated (for example, as a way to ban a user) from accounts which failed to activate within a specified window. Additionally, it is possible a user could, if manually deactivated, re-activate their account if still within the activation window; for this reason, when using the is_active field to “ban” a user, it is best to also set the user’s password to an unusable value (i.e., by calling set_unusable_password() for that user). Calling set_unusable_password() will also make it easier to query for manually-deactivated users, as their passwords will (when using Django’s default User implementation) begin with the exclamation mark (!) character.

Since the HMAC activation workflow does not use any models, it also does not make use of the admin interface and thus does not offer a convenient way to re-send an activation email. Users who have difficulty receiving the activation email can simply be manually activated by a site administrator.

However, the reduced overhead of not needing to store the activation key makes this generally preferable to the model-based workflow.

Security considerations

The activation key emailed to the user in the HMAC activation workflow is a value obtained by using Django’s cryptographic signing tools.

In particular, the activation key is of the form:

encoded_username:timestamp:signature

Where encoded_username is the username of the new account, (URL-safe) base64-encoded, timestamp is a base62-encoded timestamp of the time the user registered, and signature is a (URL-safe) base64-encoded HMAC of the username and timestamp.

Django’s implementation uses the value of the SECRET_KEY setting as the key for HMAC; additionally, it permits the specification of a salt value which can be used to “namespace” different uses of HMAC across a Django-powered site.

The HMAC activation workflow will use the value (a string) of the setting REGISTRATION_SALT as the salt, defaulting to the string "registration" if that setting is not specified. This value does not need to be kept secret (only SECRET_KEY does); it serves only to ensure that other parts of a site which also produce signed values from user input could not be used as a way to generate activation keys for arbitrary usernames (and vice-versa).