Timestamped tokens

Open Forms includes a base token generator implementation which generates timestamped salted Hashed Message Authentication Code (S-HMAC) tokens. These tokens invalidate after a configured number of days and/or when the hash input state has changed.

The tokens are especially well suited for one-time use and protect against replay attacks, without having to store state in the database.

As a developer needing such tokens, you can use the base class for the bulk of the work, which is inspired from Django’s password reset token generator.

class openforms.tokens.BaseTokenGenerator

Generate and check tokens for object-level permission checks.

Implementation adapted from django.contrib.auth.tokens.PasswordResetTokenGenerator.

When using timestamped tokens, keep in mind that:

  • the token should be single-use - once used/consumed, an attribute on the object should have been mutated that invalidates the token on replays.

  • the token expires automatically after a number of days, either configured through Django settings or information defined on the (related) object.

  • Expiry/validity is expressed in number of days - that’s the resolution. A token generated shortly before midnight is still valid for the following day.

  • the Django settings.SECRET_KEY is used to protect against tampering. KEEP IT SECRET.

Implementation guidelines

Subclasses MUST:

check_token(obj: django.db.models.base.Model, token: str) bool

Check that the token is (still) valid for the given model instance.

abstract get_hash_value_parts(obj: django.db.models.base.Model) list[str]

Obtain a list of strings reflecting object state.

Token invalidation relies on this. Instance attribute values that mutate on token consumption should be returned here. For example, for a password reset token, the hashed password value should be included since the hash changes when the password is reset (even with the same value!).

The code protected with the token should have side-effects that change the values returned here, if the token should invalidate after consumption.

get_token_timeout_days(obj: django.db.models.base.Model) int

Determine how many days the token is valid for.

Defaults to token_timeout_days, optionally you can override this method and fetch information from the object being checked.

key_salt: str = ''

Value to use as salt for the hashing process.

make_token(obj: django.db.models.base.Model) str

Return a token that expires or gets invalidated by state changes.

token_timeout_days = 1

Default number of days the token is valid. Can be overridden via get_token_timeout_days().