<< ALL BLOG POSTS

How to Create Custom Password Validators in Django

Table of Contents

Have you been "volunteered" to eliminate weak passwords? You can customize Django to enforce stronger password requirements. And Django makes it easy to add your own password validation into the mix when it is creating new users or allowing users to set a new password. 

How To Enable Password Validation

Django includes some basic validators which can come in handy, such as enforcing a minimum length. There is even one to block any password appearing on a list of the 1,000 most common passwords.

To enable password validation, simply set your Django settings as follows:

AUTH_PASSWORD_VALIDATORS = [
    {
        'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
        'OPTIONS': {
            'min_length': 9,
        }
    },
    {
        'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
    },
]

How To Create Your Own Django Password Validator

If you have more specific needs, you can create your own validators. To do so, simply create your own classes based on object and raise a ValidationError if the entered password fails.

In my app, I created validators.py, which contained four custom validators (1 digit character, 1 uppercase character, 1 lowercase and 1 symbol):

import re

from django.core.exceptions import ValidationError
from django.utils.translation import ugettext as _


class NumberValidator(object): def validate(self, password, user=None): if not re.findall('\d', password): raise ValidationError( _("The password must contain at least 1 digit, 0-9."), code='password_no_number', ) def get_help_text(self): return _( "Your password must contain at least 1 digit, 0-9." ) class UppercaseValidator(object): def validate(self, password, user=None): if not re.findall('[A-Z]', password): raise ValidationError( _("The password must contain at least 1 uppercase letter, A-Z."), code='password_no_upper', ) def get_help_text(self): return _( "Your password must contain at least 1 uppercase letter, A-Z." ) class LowercaseValidator(object): def validate(self, password, user=None): if not re.findall('[a-z]', password): raise ValidationError( _("The password must contain at least 1 lowercase letter, a-z."), code='password_no_lower', ) def get_help_text(self): return _( "Your password must contain at least 1 lowercase letter, a-z." ) class SymbolValidator(object): def validate(self, password, user=None): if not re.findall('[()[\]{}|\\`~!@#$%^&*_\-+=;:\'",<>./?]', password): raise ValidationError( _("The password must contain at least 1 symbol: " + "()[]{}|\`~!@#$%^&*_-+=;:'\",<>./?"), code='password_no_symbol', ) def get_help_text(self): return _( "Your password must contain at least 1 symbol: " + "()[]{}|\`~!@#$%^&*_-+=;:'\",<>./?" )

How to Make Your Validators Configurable

If you plan to reuse this code on different projects, you can make your validators more configurable through the Django settings. Then you can pass variables into your validator like the min_length value sent to the MinimumLengthValidator shown above.

Here I've adjusted my NumberValidator to have a configurable number of digits by adding the __init__ method and adjusting the validate method to check the length of the number of matches:

class NumberValidator(object):
    def __init__(self, min_digits=0):
        self.min_digits = min_digits

    def validate(self, password, user=None):
        if not len(re.findall('\d', password)) >= self.min_digits:
            raise ValidationError(
                _("The password must contain at least %(min_digits)d digit(s), 0-9."),
                code='password_no_number',
                params={'min_digits': self.min_digits},
            )

    def get_help_text(self):
        return _(
            "Your password must contain at least %(min_digits)d digit(s), 0-9." % {'min_digits': self.min_digits}
        )

Finally, update the AUTH_PASSWORD_VALIDATORS setting with the correct dotted path to your validators:

AUTH_PASSWORD_VALIDATORS = [
    {'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
        'OPTIONS': {
            'min_length': 12, }
     },
    {'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', },
    {'NAME': 'my.project.validators.NumberValidator',
        'OPTIONS': {
            'min_digits': 3, }},
    {'NAME': 'my.project.validators.UppercaseValidator', },
    {'NAME': 'my.project.validators.LowercaseValidator', },
    {'NAME': 'my.project.validators.SymbolValidator', },
]

Using this technique you could create a single, highly-configurable validator covering these rules and/or others to reuse on all your projects as needed.

In the future, there is no reason to let your Django users gamble with weak passwords. Help them out by adding some stronger password requirements. Here's additional good documentation on Django's password validation.

Related Posts
How can we assist you?
Thank you! Your submission has been received!
Oops! Something went wrong while submitting the form.