We have a Django application that requires a specific level of password complexity. We currently enforce this via client-side JavaScript which can easily be defeated by someone who is appropriately motivated.
I cannot seem to find any specific information about setting up server-side password strength validation using the django contrib built in views. Before I go about re-inventing the wheel, is there a proper way to handle this requirement?
By default, Django uses the PBKDF2 algorithm with a SHA256 hash, a password stretching mechanism recommended by NIST. This should be sufficient for most users: it's quite secure, requiring massive amounts of computing time to break.
They must implement two methods: validate(self, password, user=None) : validate a password. Return None if the password is valid, or raise a ValidationError with an error message if the password is not valid.
I also went with a custom form for this. In urls.py specify your custom form:
(r'^change_password/$', 'django.contrib.auth.views.password_change',
     {'password_change_form': ValidatingPasswordChangeForm}),
Inherit from PasswordChangeForm and implement validation:
from django import forms
from django.contrib import auth
class ValidatingPasswordChangeForm(auth.forms.PasswordChangeForm):
    MIN_LENGTH = 8
    def clean_new_password1(self):
        password1 = self.cleaned_data.get('new_password1')
        # At least MIN_LENGTH long
        if len(password1) < self.MIN_LENGTH:
            raise forms.ValidationError("The new password must be at least %d characters long." % self.MIN_LENGTH)
        # At least one letter and one non-letter
        first_isalpha = password1[0].isalpha()
        if all(c.isalpha() == first_isalpha for c in password1):
            raise forms.ValidationError("The new password must contain at least one letter and at least one digit or" \
                                        " punctuation character.")
        # ... any other validation you want ...
        return password1
Django 1.9 offers a built-in password validation to help prevent the usage of weak passwords by users. It's enabled by modifing the AUTH_PASSWORD_VALIDATORS setting in our project. By default Django comes with following validators:
UserAttributeSimilarityValidator, which checks the similarity between
the password and a set of attributes of the user.MinimumLengthValidator, which simply checks whether the password
meets a minimum length. This validator is configured with a custom
option: it now requires the minimum length to be nine characters,
instead of the default eight.CommonPasswordValidator, which checks
whether the password occurs in a list of common passwords. By
default, it compares to an included list of 1000 common passwords.NumericPasswordValidator, which checks whether the password isn’t
entirely numeric.This example enables all four included validators:
AUTH_PASSWORD_VALIDATORS = [
    {
        'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
        'OPTIONS': {
            'min_length': 9,
        }
    },
    {
        'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
    },
]
As some eluded to with the custom validators, here's the approach I would take...
Create a validator:
from django.core.exceptions import ValidationError
from django.utils.translation import ugettext as _
def validate_password_strength(value):
    """Validates that a password is as least 7 characters long and has at least
    1 digit and 1 letter.
    """
    min_length = 7
    if len(value) < min_length:
        raise ValidationError(_('Password must be at least {0} characters '
                                'long.').format(min_length))
    # check for digit
    if not any(char.isdigit() for char in value):
        raise ValidationError(_('Password must contain at least 1 digit.'))
    # check for letter
    if not any(char.isalpha() for char in value):
        raise ValidationError(_('Password must contain at least 1 letter.'))
Then add the validator to the form field you're looking to validate:
from django.contrib.auth.forms import SetPasswordForm
class MySetPasswordForm(SetPasswordForm):
    def __init__(self, *args, **kwargs):
        super(MySetPasswordForm, self).__init__(*args, **kwargs)
        self.fields['new_password1'].validators.append(validate_password_strength)
I'd just install django-passwords and let that handle it for you: https://github.com/dstufft/django-passwords
After that you can simply subclass the registration form and replace the field with a PasswordField.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With