Using the password validator#

class pwned_passwords_django.validators.PwnedPasswordsValidator[source]#

Django’s auth system (located in django.contrib.auth) includes a configurable password-validation framework with several built-in validators, and pwned-passwords-django provides an additional validator which checks the Pwned Passwords database. To enable it, set your AUTH_PASSWORD_VALIDATORS setting to include the new validator, like so:

AUTH_PASSWORD_VALIDATORS = [
    # ... other password validators ...
    {
        "NAME": "pwned_passwords_django.validators.PwnedPasswordsValidator",
    },
]

This will cause most high-level password-setting operations to check the Pwned Passwords database, and reject any password found there.

Warning

API failures

pwned-passwords-django needs to communicate with the Pwned Passwords API in order to check passwords. If Pwned Passwords is down or timing out (the default connection timeout is 1 second), or if any other error occurs when checking the password, this validator will fall back to using Django’s CommonPasswordValidator, which uses a smaller, locally-stored list of common passwords. Whenever this happens, a message of level logging.ERROR will appear in your logs, indicating what type of failure was encountered in talking to the Pwned Passwords API.

See the error-handling documentation for details.

This validator implements the following standard Django password-validator method. It will be called automatically by Django at appropriate times, and you should not ever need to call it yourself unless you are performing low-level/manual password changes (not recommended):

validate(password: str, user: AbstractBaseUser | None = None)[source]#

Check a proposed password against Pwned Passwords and reject the password if it has been compromised.

This method is called by most high-level account-creation and account-editing operations in Django.

Raises:

django.core.exceptions.ValidationError – when the proposed password is compromised.

Limitations#

As mentioned above, Django automatically runs password validators on certain high-level operations. These operations are:

  • Whenever a user changes or resets their password with Django’s built-in auth views and forms.

  • Whenever a new user is created via Django’s built-in UserCreationForm.

  • Whenever the createsuperuser or changepassword management commands are used.

Password validators are not run automatically when a user’s password is set or updated via other mechanisms, and in those cases the validator cannot provide automatic protection against a user choosing a breached password.

Thus the best approach is always to use the built-in Django auth components, and avoid setting/updating passwords via other mechanisms. But if you must work manually with passwords, it is recommended that you do the following:

  1. Prior to manually setting or changing a password, call django.contrib.auth.password_validation.validate_password() on the proposed new password. This will manually run all password validators configured in your AUTH_PASSWORD_VALIDATORS setting. You must be prepared to catch ValidationError when doing so.

  2. Then use the set_password() method of the user model to set the password after successfully validating it.

It is also strongly recommended that you enable the middleware provided by pwned-passwords-django, which provides a way to check every incoming HTTP POST payload for potentially-compromised passwords.

Customizing the validator#

To change the error or help messages shown to the user, you can pass OPTIONS when adding the validator to your settings:

AUTH_PASSWORD_VALIDATORS = [
    # ... other password validators ...
    {
        "NAME": "pwned_passwords_django.validators.PwnedPasswordsValidator",
        "OPTIONS": {
            "error_message": "That password was pwned",
            "help_message": "Your password can't be a commonly used password.",
        }
    },
]

The number of times the password has appeared in a breach can also be included in the error message, including a plural form:

AUTH_PASSWORD_VALIDATORS = [
    # ... other password validators ...
    {
        "NAME": "pwned_passwords_django.validators.PwnedPasswordsValidator",
        "OPTIONS": {
            "error_message": (
               "Pwned %(amount)d time",
               "Pwned %(amount)d times",
            )
        }
    },
]