$ cat /posts/django-password-management-reset-change-and-security.md

Django Password Management: Reset, Change, and Security

drwxr-xr-x2026-01-235 min0 views
Django Password Management: Reset, Change, and Security

Secure password management is fundamental to web application security protecting user accounts from unauthorized access and data breaches. Django provides comprehensive password handling infrastructure including secure hashing algorithms, password validation rules, built-in views for password changes and resets, token-based reset workflows, and customizable validation policies. This guide explores Django 6.0's password management capabilities including implementing password change functionality for authenticated users, building secure password reset workflows with email verification, configuring password validation rules enforcing strength requirements, understanding Django's password hashing mechanisms using PBKDF2 and Argon2, customizing password validators for specific security policies, handling password history and expiration, securing password reset tokens and links, and best practices for password security ensuring user accounts remain protected against common attacks. Mastering password management enables building secure authentication systems that balance security requirements with user experience providing robust account protection while maintaining usability throughout the application lifecycle.

Password Change for Authenticated Users

Django's PasswordChangeView provides authenticated users the ability to update their passwords by verifying their current password and setting a new one. This view includes built-in validation ensuring users know their current password before changing, password strength verification, and automatic session updates maintaining user login after password changes. The process requires authentication, validates old password correctness, enforces password validation rules, and redirects to success pages after completion.

pythonpassword_change.py
# Using built-in PasswordChangeView
from django.contrib.auth import views as auth_views
from django.urls import path, reverse_lazy

# urls.py
urlpatterns = [
    path('password-change/',
         auth_views.PasswordChangeView.as_view(
             template_name='accounts/password_change.html',
             success_url=reverse_lazy('password-change-done')
         ),
         name='password-change'),
    
    path('password-change/done/',
         auth_views.PasswordChangeDoneView.as_view(
             template_name='accounts/password_change_done.html'
         ),
         name='password-change-done'),
]

# Custom PasswordChangeView
from django.contrib.auth.views import PasswordChangeView
from django.contrib import messages

class CustomPasswordChangeView(PasswordChangeView):
    template_name = 'accounts/password_change.html'
    success_url = reverse_lazy('profile')
    
    def form_valid(self, form):
        messages.success(self.request, 'Your password has been changed successfully!')
        # Send email notification
        user = self.request.user
        send_mail(
            'Password Changed',
            f'Your password was changed on {timezone.now()}.',
            '[email protected]',
            [user.email],
        )
        return super().form_valid(form)

# Template: password_change.html
"""
{% extends 'base.html' %}

{% block content %}
<h2>Change Password</h2>
<form method="post">
    {% csrf_token %}
    {{ form.as_p }}
    <button type="submit">Change Password</button>
</form>
{% endblock %}
"""

# Function-based password change (alternative)
from django.contrib.auth import update_session_auth_hash
from django.contrib.auth.forms import PasswordChangeForm
from django.contrib.auth.decorators import login_required

@login_required
def change_password(request):
    if request.method == 'POST':
        form = PasswordChangeForm(request.user, request.POST)
        if form.is_valid():
            user = form.save()
            # Important: Update session to prevent logout
            update_session_auth_hash(request, user)
            messages.success(request, 'Password changed successfully!')
            return redirect('profile')
    else:
        form = PasswordChangeForm(request.user)
    return render(request, 'accounts/password_change.html', {'form': form})
Always call update_session_auth_hash after password changes to prevent users from being logged out. PasswordChangeView does this automatically but custom implementations require manual session updates.

Password Reset Workflow

Password reset enables users who forgot passwords to regain account access through email verification. Django provides four views handling the complete reset workflow: PasswordResetView collects email addresses, PasswordResetDoneView confirms email sent, PasswordResetConfirmView processes reset links from emails, and PasswordResetCompleteView confirms successful resets. This token-based system ensures security through time-limited single-use tokens preventing unauthorized password changes.

pythonpassword_reset.py
# Complete password reset workflow
from django.contrib.auth import views as auth_views
from django.urls import path, reverse_lazy

# urls.py
urlpatterns = [
    # Step 1: Request password reset
    path('password-reset/',
         auth_views.PasswordResetView.as_view(
             template_name='accounts/password_reset.html',
             email_template_name='accounts/password_reset_email.html',
             subject_template_name='accounts/password_reset_subject.txt',
             success_url=reverse_lazy('password-reset-done')
         ),
         name='password-reset'),
    
    # Step 2: Confirmation that email was sent
    path('password-reset/done/',
         auth_views.PasswordResetDoneView.as_view(
             template_name='accounts/password_reset_done.html'
         ),
         name='password-reset-done'),
    
    # Step 3: Reset link from email
    path('password-reset-confirm/<uidb64>/<token>/',
         auth_views.PasswordResetConfirmView.as_view(
             template_name='accounts/password_reset_confirm.html',
             success_url=reverse_lazy('password-reset-complete')
         ),
         name='password-reset-confirm'),
    
    # Step 4: Password reset complete
    path('password-reset-complete/',
         auth_views.PasswordResetCompleteView.as_view(
             template_name='accounts/password_reset_complete.html'
         ),
         name='password-reset-complete'),
]

# Custom PasswordResetView
from django.contrib.auth.views import PasswordResetView

class CustomPasswordResetView(PasswordResetView):
    template_name = 'accounts/password_reset.html'
    email_template_name = 'accounts/password_reset_email.html'
    success_url = reverse_lazy('password-reset-done')
    
    def form_valid(self, form):
        # Add custom logic before sending email
        email = form.cleaned_data['email']
        # Log reset attempt
        logger.info(f'Password reset requested for {email}')
        return super().form_valid(form)

# Template: password_reset_email.html
"""
Hi {{ user.username }},

You requested a password reset for your account.

Click the link below to reset your password:
{{ protocol }}://{{ domain }}{% url 'password-reset-confirm' uidb64=uid token=token %}

This link expires in 24 hours.

If you didn't request this, please ignore this email.
"""

# Template: password_reset.html
"""
{% extends 'base.html' %}

{% block content %}
<h2>Forgot Password?</h2>
<p>Enter your email address and we'll send you a link to reset your password.</p>
<form method="post">
    {% csrf_token %}
    {{ form.as_p }}
    <button type="submit">Send Reset Link</button>
</form>
{% endblock %}
"""

# settings.py - Email configuration
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_HOST = 'smtp.gmail.com'
EMAIL_PORT = 587
EMAIL_USE_TLS = True
EMAIL_HOST_USER = '[email protected]'
EMAIL_HOST_PASSWORD = 'your-app-password'
DEFAULT_FROM_EMAIL = '[email protected]'

# Password reset timeout (default 3 days)
PASSWORD_RESET_TIMEOUT = 86400  # 24 hours in seconds

Password Validation and Security

Django includes built-in password validators enforcing security policies including minimum length requirements, common password detection, user attribute similarity checks, and numeric-only password prevention. These validators protect against weak passwords that are vulnerable to brute force and dictionary attacks. Custom validators enable organization-specific policies like special character requirements, password history checks, or complexity rules.

pythonpassword_validation.py
# settings.py - Password validation configuration
AUTH_PASSWORD_VALIDATORS = [
    {
        'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
        'OPTIONS': {
            'user_attributes': ('username', 'email', 'first_name', 'last_name'),
            'max_similarity': 0.7,
        }
    },
    {
        'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
        'OPTIONS': {
            'min_length': 12,
        }
    },
    {
        'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
    },
]

# Custom password validator
from django.core.exceptions import ValidationError
from django.utils.translation import gettext as _
import re

class ComplexityValidator:
    """Require uppercase, lowercase, digit, and special character"""
    
    def validate(self, password, user=None):
        if not re.search(r'[A-Z]', password):
            raise ValidationError(
                _('Password must contain at least one uppercase letter.'),
                code='password_no_upper',
            )
        if not re.search(r'[a-z]', password):
            raise ValidationError(
                _('Password must contain at least one lowercase letter.'),
                code='password_no_lower',
            )
        if not re.search(r'\d', password):
            raise ValidationError(
                _('Password must contain at least one digit.'),
                code='password_no_digit',
            )
        if not re.search(r'[!@#$%^&*(),.?":{}|<>]', password):
            raise ValidationError(
                _('Password must contain at least one special character.'),
                code='password_no_special',
            )
    
    def get_help_text(self):
        return _(
            'Your password must contain at least one uppercase letter, '
            'one lowercase letter, one digit, and one special character.'
        )

# Password history validator
class PasswordHistoryValidator:
    """Prevent reuse of recent passwords"""
    
    def __init__(self, history_length=5):
        self.history_length = history_length
    
    def validate(self, password, user=None):
        if user is None:
            return
        
        # Check against password history
        from .models import PasswordHistory
        recent_passwords = PasswordHistory.objects.filter(
            user=user
        ).order_by('-created_at')[:self.history_length]
        
        for old_password in recent_passwords:
            if user.check_password(password):
                raise ValidationError(
                    _('You cannot reuse a recent password.'),
                    code='password_reused',
                )
    
    def get_help_text(self):
        return _(
            f'Your password cannot be one of your last {self.history_length} passwords.'
        )

# Add custom validators to settings
AUTH_PASSWORD_VALIDATORS += [
    {
        'NAME': 'accounts.validators.ComplexityValidator',
    },
    {
        'NAME': 'accounts.validators.PasswordHistoryValidator',
        'OPTIONS': {
            'history_length': 5,
        }
    },
]

# Using password validation programmatically
from django.contrib.auth.password_validation import validate_password
from django.core.exceptions import ValidationError

def check_password_strength(password, user=None):
    try:
        validate_password(password, user=user)
        return True, 'Password is valid'
    except ValidationError as e:
        return False, e.messages

# In views
password = request.POST.get('password')
is_valid, errors = check_password_strength(password, user=request.user)
if not is_valid:
    for error in errors:
        messages.error(request, error)

Password Hashing and Storage

Django never stores passwords in plain text using one-way hashing algorithms to protect credentials even if databases are compromised. The default hasher uses PBKDF2 with SHA256 providing strong security through key derivation with salt and iterations. Django 6.0 supports multiple hashers including Argon2 recommended for maximum security, BCrypt for compatibility, and PBKDF2 for balanced security and performance. Passwords are automatically rehashed when stronger algorithms become available ensuring continuous security improvements.

pythonpassword_hashing.py
# settings.py - Password hasher configuration
PASSWORD_HASHERS = [
    'django.contrib.auth.hashers.Argon2PasswordHasher',  # Most secure
    'django.contrib.auth.hashers.PBKDF2PasswordHasher',  # Default
    'django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher',
    'django.contrib.auth.hashers.BCryptSHA256PasswordHasher',
]

# Install argon2-cffi for Argon2 support
# pip install argon2-cffi

# Manual password operations
from django.contrib.auth.models import User
from django.contrib.auth.hashers import make_password, check_password

# Create user with hashed password
user = User.objects.create(
    username='john',
    password=make_password('secure_password')
)

# Set password (automatically hashes)
user.set_password('new_password')
user.save()

# Check password
if user.check_password('new_password'):
    print('Password correct')

# Get password hash
password_hash = user.password
print(password_hash)  # 'pbkdf2_sha256$260000$...'

# Password hash format:
# <algorithm>$<iterations>$<salt>$<hash>

# Programmatic password checking
is_correct = check_password('password', user.password)

# Password upgrade mechanism
from django.contrib.auth import authenticate

user = authenticate(username='john', password='password')
if user and user.password_needs_rehash():
    # Password still uses old algorithm, rehash it
    user.set_password('password')
    user.save()
Use Argon2 as the primary password hasher for new projects. It won the Password Hashing Competition and provides the best security against GPU-based attacks. Install argon2-cffi and place Argon2PasswordHasher first in PASSWORD_HASHERS.

Password Security Best Practices

  1. Use strong hashers: Configure Argon2 as primary hasher providing maximum security against brute force attacks
  2. Enforce password complexity: Require minimum length, character variety, and prevent common passwords through validators
  3. Secure reset tokens: Use time-limited single-use tokens for password resets preventing token reuse attacks
  4. Email notifications: Send email alerts when passwords change detecting unauthorized access attempts
  5. Rate limiting: Implement rate limiting on password reset requests preventing abuse and enumeration attacks
  6. HTTPS only: Always serve password forms over HTTPS protecting credentials during transmission

Conclusion

Django's password management system provides comprehensive infrastructure for secure credential handling protecting user accounts from common attacks. PasswordChangeView enables authenticated users to update passwords securely while maintaining sessions through automatic session updates. The password reset workflow uses token-based email verification ensuring only legitimate users regain account access through time-limited single-use tokens. Password validators enforce security policies including length requirements, complexity rules, and common password detection preventing weak credentials. Django's password hashing using PBKDF2, Argon2, or BCrypt ensures passwords remain protected even if databases are compromised through one-way cryptographic hashing. Custom validators enable organization-specific policies while built-in validators provide baseline security. Following best practices including strong hashers, complexity enforcement, secure token handling, email notifications, rate limiting, and HTTPS ensures robust password security. Understanding password management enables building secure authentication systems balancing security requirements with user experience throughout Django 6.0 applications protecting user accounts against unauthorized access attempts.

$ cat /comments/ (0)

new_comment.sh

// Email hidden from public

>_

$ cat /comments/

// No comments found. Be the first!

[session] guest@{codershandbook}[timestamp] 2026

Navigation

Connect

Subscribe

// 2026 {Coders Handbook}. EOF.