Django's Built-in Password Validation — What It Is and How It Works

A beginner-friendly guide to keeping your users' passwords safe without writing a single validation rule yourself.


The Problem: Weak Passwords Are Everywhere

If you let users pick any password they want, some will choose 123456, their own name, or just the word password. These are trivially easy to crack.

Instead of writing your own checks from scratch, Django gives you a plug-and-play system called password validation. You configure it once in settings.py, and Django enforces it everywhere — registration, password changes, the admin panel.


The Four Built-in Validators

These live in your settings.py under AUTH_PASSWORD_VALIDATORS:

AUTH_PASSWORD_VALIDATORS = [
    {
        'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
    },
]

Let's go through each one.


1. UserAttributeSimilarityValidator

What it does: Rejects passwords that are too similar to the user's own data — their username, email, first name, or last name.

Why it matters: If someone's username is parthi123 and their password is parthi123, an attacker who knows the username can guess the password instantly.

Example: - Username: john_doe - Password: johndoe2024 ❌ — too similar, rejected - Password: BlueSky#99 ✅ — no relation to the user's info


2. MinimumLengthValidator

What it does: Rejects passwords that are too short. The default minimum is 8 characters.

Why it matters: Shorter passwords have fewer possible combinations, making them much faster to crack by brute force (trying every possible combination).

You can customise the minimum length like this:

{
    'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
    'OPTIONS': {
        'min_length': 12,
    }
},

Example: - Password: abc ❌ — too short - Password: MyP@ss99 ✅ — 8 characters, passes


3. CommonPasswordValidator

What it does: Rejects passwords that appear on a list of the 20,000 most commonly used passwords.

Why it matters: Attackers use "dictionary attacks" — they try known common passwords first, because so many people use them. Django ships with this list built in, so you don't have to maintain it yourself.

Example: - Password: password ❌ — on the common list - Password: iloveyou ❌ — also on the list - Password: Tr0ub4dor&3 ✅ — not on the list


4. NumericPasswordValidator

What it does: Rejects passwords that are entirely numbers.

Why it matters: A password like 19901231 (a date of birth) looks complex, but it's just numbers — and people commonly use meaningful dates as passwords. This validator prevents that category entirely.

Example: - Password: 19901231 ❌ — all numeric - Password: 1990dec31 ✅ — has letters, passes


How Django Actually Runs These Checks

The validators don't run by magic. You have to call them manually in your view using the validate_password() function. Here's the pattern you'll use:

from django.contrib.auth.password_validation import validate_password
from django.core.exceptions import ValidationError

def register(request):
    if request.method == 'POST':
        password = request.POST.get('password')

        try:
            validate_password(password)
        except ValidationError as e:
            for error in e.messages:
                messages.error(request, error)
            return redirect('register')

        # If we reach here, the password passed all validators
        # ... create the user

Breaking it down, line by line

Code What it does
validate_password(password) Runs the password through all four validators in order
try: "Attempt the following — and be ready to handle failure"
except ValidationError as e: "If any validator fails, catch the error and call it e"
e.messages A list of human-readable error strings, one per failed rule
messages.error(request, error) Adds each error to Django's flash message system so your template can display it
return redirect('register') Sends the user back to the form to try again

If no exception is raised, the code simply falls through — the password is valid, and you can safely create the user.


What e.messages Looks Like

If a user tries to register with the password abc, e.messages might look like:

[
    "This password is too short. It must contain at least 8 characters.",
    "This password is too common."
]

Each message is shown to the user as a separate flash message. Your template just needs something like:

{% for message in messages %}
  <div class="alert alert-danger">{{ message }}</div>
{% endfor %}

A Common Mistake to Avoid

Beginners sometimes skip validate_password() and rely on Django's form or create_user() to handle validation automatically. This works in some contexts, but when you're building a custom registration view with raw POST data, you must call validate_password() yourself — it won't happen on its own.


Quick Summary

Validator Blocks
UserAttributeSimilarityValidator Passwords too similar to username/email/name
MinimumLengthValidator Passwords under 8 characters (configurable)
CommonPasswordValidator Passwords from a list of 20,000 common ones
NumericPasswordValidator Passwords made entirely of numbers

And the try/except ValidationError pattern is how you hook these validators into your own view — catching failures, surfacing the messages to the user, and redirecting them back to try again.