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.