Skip to main content

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:

python
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:

python
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:

python
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:

python
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:

  1. MinimumLengthValidator: Ensures passwords meet a minimum length
  2. CommonPasswordValidator: Checks if the password is too common
  3. NumericPasswordValidator: Ensures the password isn't entirely numeric
  4. UserAttributeSimilarityValidator: Prevents passwords similar to user attributes

These validators are configured in settings.py:

python
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:

python
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:

python
AUTH_PASSWORD_VALIDATORS = [
# ... existing validators ...
{
'NAME': 'path.to.your.SpecialCharacterValidator',
},
]

Validating Passwords Programmatically

To validate a password programmatically:

python
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:

python
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:

  1. registration/password_reset_form.html - The form for entering the email address
  2. registration/password_reset_done.html - Confirmation page after requesting a reset
  3. registration/password_reset_email.html - The email template with the reset link
  4. registration/password_reset_confirm.html - The page for entering a new password
  5. registration/password_reset_complete.html - Success page after resetting the password

Here's a simple example of a password reset form:

html
<!-- 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:

python
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

python
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:

  1. registration/password_change_form.html - Form for changing the password
  2. registration/password_change_done.html - Success page after changing the password

Example template:

html
<!-- 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

python
# settings.py (additional settings)
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' # For development
LOGIN_REDIRECT_URL = 'home'
LOGOUT_REDIRECT_URL = 'home'

URLs Setup

python
# 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

python
# 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

html
<!-- 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 %}
html
<!-- 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

  1. Use HTTPS: Always use HTTPS for login and password reset pages to prevent credentials from being intercepted.

  2. Rate Limiting: Implement rate limiting to prevent brute force attacks:

python
# 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
  1. Password Strength Meters: Consider adding a password strength meter to help users create strong passwords.

  2. Two-Factor Authentication: For additional security, consider implementing two-factor authentication using libraries like django-two-factor-auth.

  3. Session Management: Set appropriate session timeout periods in your settings:

python
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

  1. Django Authentication Documentation
  2. Django Password Management Documentation
  3. OWASP Password Storage Cheat Sheet

Practice Exercises

  1. Create a custom password validator that requires passwords to contain at least one uppercase letter, one lowercase letter, and one digit.

  2. Implement a "password age" feature that requires users to change their password every 90 days.

  3. Extend the User model to keep track of password history and prevent users from reusing their last 3 passwords.

  4. Create a custom password strength meter using JavaScript to give users real-time feedback as they type their password.

  5. 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! :)