Django stores passwords with PBKDF2 and a SHA256 hash by default, and projects created with startproject enable a set of password validators. But validators run in forms and management commands, not in create_user, so code that creates accounts directly bypasses them. Check both settings in your project.
Hashing: PBKDF2 by default
Django's password documentation says that by default it uses the PBKDF2 algorithm with a SHA256 hash, a password stretching mechanism recommended by NIST, and that this should be sufficient for most users. The PASSWORD_HASHERS default lists PBKDF2 first, so new passwords use it, while the other entries let Django verify older hashes.
If you want a different algorithm, put it first. The docs show Argon2 as an option: install argon2-cffi and list the Argon2 hasher first. Keep the older entries so existing passwords still verify and can be upgraded when users sign in. If a stored hash names an algorithm missing from the list, verification raises a ValueError.
Validation: on in new projects, off in old settings
AUTH_PASSWORD_VALIDATORS defaults to an empty list, meaning none apply. But new projects generated by startproject enable a set. The four included validators are:
| Validator | Checks |
|---|---|
| UserAttributeSimilarityValidator | Similarity to the user's attributes |
| MinimumLengthValidator | Minimum length, configurable |
| CommonPasswordValidator | Common passwords |
| NumericPasswordValidator | Passwords that are entirely numeric |
An old project created before these were in the template may have an empty list. That is worth a look today.
The create_user gap
The docs state that validators are used in the forms to reset or change passwords and in the createsuperuser and changepassword commands. They are not applied at the model level, such as User.objects.create_user() and create_superuser(), because Django assumes developers, not users, work at that level and because model validation does not run automatically.
So if your signup view builds the user with create_user directly, nothing enforces your rules. Call the validation yourself:
from django.contrib.auth.password_validation import validate_password
validate_password(password, user=user)
user = User.objects.create_user(username=name, password=password)
Better, use a form that does it for you.
Passing validators is not the same as strong
Django's docs include a note that passing all validators does not guarantee a strong password. A long passphrase unique to your site is stronger than a short password that squeezes past the rules. Consider raising the minimum length above the example and adding rate limiting, multi factor authentication and breach checks outside Django's built in tools.
Review checklist
- Read AUTH_PASSWORD_VALIDATORS in each settings module. Empty means none.
- Check PASSWORD_HASHERS. Is the first entry the one you intend?
- Find every call to create_user and create_superuser outside forms and confirm validation runs.
- Check imports of old password hashes. Unsalted or fast hashes should be upgraded on first login.
- Rate limit login and reset endpoints.
Try it yourself
Click through it at your own pace, or open it full screen. Look at how authentication related code paths get flagged for review.
Quick check
Does changing PASSWORD_HASHERS rehash existing passwords?
Not immediately. Django upgrades a hash when the user next signs in, if the old algorithm is still listed so verification works.
Do validators apply when I call create_user?
No. Django applies them in forms and in createsuperuser and changepassword, not at the model level.
Safeguard's SAST and compliance evidence automation can help flag weak authentication code and record the settings you rely on. A free scan is available.
Sources
Last reviewed: 19 October 2026.