Django Password Management
Introduction
Password management is a critical aspect of web application security. Django provides a robust set of tools and utilities to handle passwords securely, from hashing and validation to reset workflows. In this tutorial, we'll explore how Django helps you manage user passwords safely and efficiently.
Django's password management system is built on industry best practices, ensuring that your users' credentials are stored and handled securely. Whether you're building a simple blog or a complex enterprise application, understanding password management in Django is essential for maintaining application security.
Password Hashing in Django
How Django Stores Passwords
Django never stores passwords in plain text. Instead, it uses password hashing algorithms to convert passwords into a secure, irreversible format before storing them in the database.
Default Password Hasher
By default, Django uses the PBKDF2 algorithm with SHA-256 hash, which is considered secure by modern standards. The algorithm applies multiple iterations to increase security.
You can view the password hashers Django uses in your settings.py
file:
PASSWORD_HASHERS = [
'django.contrib.auth.hashers.PBKDF2PasswordHasher',
'django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher',
'django.contrib.auth.hashers.Argon2PasswordHasher',
'django.contrib.auth.hashers.BCryptSHA256PasswordHasher',
]
The order in this list is important as Django will use the first hasher by default when creating new passwords.
Creating and Updating Passwords
Creating a User with a Password
When creating a new user, you can set their password using the set_password()
method:
from django.contrib.auth.models import User
# Create a new user
user = User.objects.create_user(
username='johndoe',
email='[email protected]',
password='secure_password123' # This will be hashed automatically
)
The create_user()
method automatically hashes the password before storing it.
Updating a User's Password
To update an existing user's password:
user = User.objects.get(username='johndoe')
user.set_password('new_secure_password456')
user.save() # Don't forget to save the user object!
Never assign to user.password
directly, as this would store the password in plain text!
Password Verification
To check if a provided password matches the stored hash:
user = User.objects.get(username='johndoe')
if user.check_password('provided_password'):
print("Password is correct!")
else:
print("Incorrect password")
Password Validation
Django provides a system to validate passwords against common security requirements.
Default Validators
Django includes several built-in password validators:
- MinimumLengthValidator: Ensures passwords meet a minimum length
- CommonPasswordValidator: Checks if the password is too common
- NumericPasswordValidator: Ensures the password isn't entirely numeric
- UserAttributeSimilarityValidator: Prevents passwords similar to user attributes
These validators are configured in settings.py
:
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
'OPTIONS': {
'min_length': 8,
}
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
]
Creating Custom Validators
You can create custom password validators by implementing a class with a validate()
method and an optional get_help_text()
method:
from django.core.exceptions import ValidationError
class SpecialCharacterValidator:
def validate(self, password, user=None):
if not any(char in "!@#$%^&*()-_=+[]{}|;:,.<>?/" for char in password):
raise ValidationError(
"Password must contain at least one special character.",
code='password_no_special_char',
)
def get_help_text(self):
return "Your password must contain at least one special character."
Add this validator to the AUTH_PASSWORD_VALIDATORS
list in settings.py
:
AUTH_PASSWORD_VALIDATORS = [
# ... existing validators ...
{
'NAME': 'path.to.your.SpecialCharacterValidator',
},
]
Validating Passwords Programmatically
To validate a password programmatically:
from django.contrib.auth.password_validation import validate_password
from django.core.exceptions import ValidationError
try:
validate_password("weakpw")
except ValidationError as e:
print(f"Password validation errors: {e}")
Output:
Password validation errors: ['This password is too short. It must contain at least 8 characters.', 'This password is too common.']
Password Reset Workflow
Django provides a complete password reset workflow for users who have forgotten their passwords.
URL Configuration
First, include Django's authentication URLs in your project's urls.py
:
from django.urls import path, include
from django.contrib.auth import views as auth_views
urlpatterns = [
# ... other URL patterns ...
path('accounts/', include('django.contrib.auth.urls')),
# Or define individual URLs:
# path('password_reset/', auth_views.PasswordResetView.as_view(), name='password_reset'),
# path('password_reset/done/', auth_views.PasswordResetDoneView.as_view(), name='password_reset_done'),
# path('reset/<uidb64>/<token>/', auth_views.PasswordResetConfirmView.as_view(), name='password_reset_confirm'),
# path('reset/done/', auth_views.PasswordResetCompleteView.as_view(), name='password_reset_complete'),
]
Required Templates
You'll need to create the following templates for the password reset flow:
- registration/password_reset_form.html - The form for entering the email address
- registration/password_reset_done.html - Confirmation page after requesting a reset
- registration/password_reset_email.html - The email template with the reset link
- registration/password_reset_confirm.html - The page for entering a new password
- registration/password_reset_complete.html - Success page after resetting the password
Here's a simple example of a password reset form:
<!-- registration/password_reset_form.html -->
<h2>Reset Password</h2>
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<button type="submit">Send reset email</button>
</form>
Customizing the Password Reset Process
You can customize the password reset process by subclassing Django's built-in views:
from django.contrib.auth.views import PasswordResetView
from django.urls import reverse_lazy
class CustomPasswordResetView(PasswordResetView):
email_template_name = 'custom_email_template.html'
success_url = reverse_lazy('custom_reset_done')
from_email = '[email protected]'
Password Change Workflow
Django also provides views for authenticated users to change their password.
URL Configuration
urlpatterns = [
# ... other URL patterns ...
path('accounts/password_change/', auth_views.PasswordChangeView.as_view(), name='password_change'),
path('accounts/password_change/done/', auth_views.PasswordChangeDoneView.as_view(), name='password_change_done'),
]
Required Templates
You'll need to create these templates:
- registration/password_change_form.html - Form for changing the password
- registration/password_change_done.html - Success page after changing the password
Example template:
<!-- registration/password_change_form.html -->
<h2>Change Password</h2>
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<button type="submit">Change password</button>
</form>
Real-World Example: Complete Authentication System
Let's build a complete authentication system with registration, login, password change, and password reset.
Project Setup
# settings.py (additional settings)
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' # For development
LOGIN_REDIRECT_URL = 'home'
LOGOUT_REDIRECT_URL = 'home'
URLs Setup
# urls.py
from django.urls import path, include
from django.contrib.auth import views as auth_views
from . import views
urlpatterns = [
path('', views.home, name='home'),
path('accounts/', include('django.contrib.auth.urls')),
path('accounts/signup/', views.signup, name='signup'),
]
Views
# views.py
from django.shortcuts import render, redirect
from django.contrib.auth.forms import UserCreationForm
from django.contrib.auth.decorators import login_required
from django.contrib.auth import login
def home(request):
return render(request, 'home.html')
def signup(request):
if request.method == 'POST':
form = UserCreationForm(request.POST)
if form.is_valid():
user = form.save()
login(request, user)
return redirect('home')
else:
form = UserCreationForm()
return render(request, 'registration/signup.html', {'form': form})
@login_required
def profile(request):
return render(request, 'profile.html')
Templates
<!-- home.html -->
<h2>Home Page</h2>
{% if user.is_authenticated %}
<p>Welcome, {{ user.username }}</p>
<p><a href="{% url 'password_change' %}">Change password</a></p>
<p><a href="{% url 'logout' %}">Log out</a></p>
{% else %}
<p>You are not logged in.</p>
<p><a href="{% url 'login' %}">Log in</a> or <a href="{% url 'signup' %}">Sign up</a></p>
<p><a href="{% url 'password_reset' %}">Forgot password?</a></p>
{% endif %}
<!-- registration/signup.html -->
<h2>Sign up</h2>
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<button type="submit">Sign up</button>
</form>
<p>Already have an account? <a href="{% url 'login' %}">Log in</a></p>
Security Best Practices
-
Use HTTPS: Always use HTTPS for login and password reset pages to prevent credentials from being intercepted.
-
Rate Limiting: Implement rate limiting to prevent brute force attacks:
# Example using Django-ratelimit
from ratelimit.decorators import ratelimit
@ratelimit(key='ip', rate='5/m', method='POST', block=True)
def login_view(request):
# Login logic here
pass
-
Password Strength Meters: Consider adding a password strength meter to help users create strong passwords.
-
Two-Factor Authentication: For additional security, consider implementing two-factor authentication using libraries like
django-two-factor-auth
. -
Session Management: Set appropriate session timeout periods in your settings:
SESSION_COOKIE_AGE = 3600 # 1 hour in seconds
SESSION_EXPIRE_AT_BROWSER_CLOSE = True
Summary
Django provides a comprehensive set of tools for password management:
- Secure password hashing using PBKDF2 or other algorithms
- Password validation to enforce security requirements
- Complete workflows for password reset and change
- Authentication views and forms that follow security best practices
These features allow you to implement secure password management in your Django applications without having to build everything from scratch.
Additional Resources
- Django Authentication Documentation
- Django Password Management Documentation
- OWASP Password Storage Cheat Sheet
Practice Exercises
-
Create a custom password validator that requires passwords to contain at least one uppercase letter, one lowercase letter, and one digit.
-
Implement a "password age" feature that requires users to change their password every 90 days.
-
Extend the User model to keep track of password history and prevent users from reusing their last 3 passwords.
-
Create a custom password strength meter using JavaScript to give users real-time feedback as they type their password.
-
Implement a system to temporarily lock accounts after multiple failed login attempts.
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)