50

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?

jslatts
  • 9,307
  • 5
  • 35
  • 38

5 Answers5

42

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
EMP
  • 59,148
  • 53
  • 164
  • 220
  • 3
    Don't forget about ``django.contrib.auth.forms.SetPasswordForm`` – Pawel Furmaniak Mar 15 '13 at 08:43
  • 6
    For those who might not know what @uszywieloryba is talking about, it's the form used for password resets. If you use this answer's code, just use `class ValidatingPasswordForm(object):` instead of `class ValidatingPasswordChangeForm(auth.forms.PasswordChangeForm):`. Then define `class ValidatingPasswordChangeForm(ValidatingPasswordForm, auth.forms.PasswordChangeForm):` and `class ValidatingSetPasswordForm(ValidatingPasswordForm, auth.forms.SetPasswordForm):`, both containing just `pass`. – Nick May 23 '14 at 20:56
37

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',
    },
]
phoenix
  • 7,988
  • 6
  • 39
  • 45
Cesar Canassa
  • 18,659
  • 11
  • 66
  • 69
  • 9
    If I'm creating the user via an API with the User.objects.create_user() method, how do I get it to run the password validation on that method call? – Collin Aug 16 '16 at 21:51
  • 2
    @Collin - You need to call the validation methods yourself, they are not automatically run just because you have put them in your settings. Have a look at the docs for [Integrating Validation](https://docs.djangoproject.com/en/dev/topics/auth/passwords/#integrating-validation). – Tony Feb 28 '17 at 22:15
16

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)
Troy Grosfield
  • 2,133
  • 20
  • 19
  • 1
    Is there any more I have to do to use this? I added this code to my app's admin.py but it's not getting used. I can't figure out how to get MySetPasswordForm to be used. – Larry Martell Apr 10 '15 at 19:30
  • @LarryMartell, it works for me as is. Are you sure you have a field called "new_password1" in your form? If you set a breakpoint in the validator does it hit that breakpoint? – Troy Grosfield Feb 12 '16 at 17:07
  • @TroyGrosfield typo: "must container" --> "must contain" – Spaceman Mar 27 '16 at 21:15
  • 1
    Excellent answer. Now I can customize to my needs. – slogan621 May 11 '19 at 17:50
10

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.

Alper
  • 3,424
  • 4
  • 39
  • 45
3

I think you should just write your own validator ( or use RegexValidator, see: http://docs.djangoproject.com/en/dev/ref/validators/ ) if you use forms or write some other script checking for regular expressions. This should be a simple task. Also I don't think there is any builtin mechanism, simply because each person understands the concept of "strong password" a little bit different.

freakish
  • 54,167
  • 9
  • 132
  • 169
  • I will look into this method. I am not familiar with Django and I was in a hurry, so I ended up just implementing my own version of the django.contrib.auth.views.password_change view where I simply validate the password before allowing it to save. It is probably much more code than needed. – jslatts Mar 08 '11 at 00:20
  • I looked into this some more, and I think the correct answer does involve validators, but you will still need to provide a custom form to use the validator on. I am going to write up my answer shortly. – jslatts Mar 09 '11 at 21:08
  • @jslatts did you come up with an answer? – Stephen Paulger Jun 07 '11 at 16:04