Using the middleware#

To help with situations where a potentially-compromised password is used in ways Django’s password validators won’t catch, pwned-passwords-django also provides a middleware which monitors every incoming HTTP request for POST payloads which appear to contain passwords, and checks them against Pwned Passwords.

pwned_passwords_django.middleware.pwned_passwords_middleware(get_response: Callable) Callable[source]#

Factory function returning a middleware – sync or async as necessary – which checks POST submissions that potentially contain passwords against the Pwned Passwords database.

To enable the middleware, add "pwned_passwords_django.middleware.pwned_passwords_middleware" to your MIDDLEWARE setting. This will add a new attribute – pwned_passwords – to each HttpRequest object. The request.pwned_passwords attribute will be a list of str.

Warning

Middleware order

The order of middleware classes in the Django MIDDLEWARE setting can be sensitive. In particular, any middlewares which affect file upload handlers must be listed above middlewares which inspect POST. Since this middleware has to inspect POST for likely passwords, it must be listed after any middlewares which might change upload handlers. If you’re unsure what this means, just put this middleware at the bottom of your MIDDLEWARE list.

The request.pwned_passwords list will be empty if any of the following is true:

  • The request method is not POST.

  • The request method is POST, but the payload does not appear to contain a password.

  • The request method is POST, and the payload appears to contain one or more passwords, but none were listed as compromised in Pwned Passwords.

If the request method is POST, and the payload appears to contain one or more passwords, and at least one of those is listed in Pwned Passwords, then request.pwned_passwords will be a list of keys from request.POST that contained compromised passwords.

For example, if request.POST contains a key named password_field, and request.POST["password_field"] is a password that appears in the Pwned Passwords database, request.pwned_passwords will be ["password_field"].

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

Here’s an example of how you might use Django’s message framework to indicate to a user that they’ve just submitted a password that appears to be compromised:

from django.contrib import messages


def some_view(request):
    if request.method == "POST" and request.pwned_passwords:
        messages.warning(
            request,
            "You just entered a password which appears to be compromised!"
        )

pwned-passwords-django uses a regular expression to guess which items in POST are likely to be passwords. By default, it matches on any key in POST containing "PASS" (case-insensitive), which catches input names like "password", "passphrase", and so on. If you use something significantly different than this for a password input name, specify it – as a string, not as a compiled regex object! – in the setting settings.PWNED_PASSWORDS["PASSWORD_REGEX"] to tell the middleware what to look for. See the settings documentation for details.