Django password complexity validation illustration
Home Django Enforcing Password Complexity in Django Projects

Enforcing Password Complexity in Django Projects

18 August, 2025

Password security is a critical aspect of any web application. Django provides several built-in mechanisms for enforcing password complexity, but understanding how to implement and customize these rules is essential for maintaining strong security standards. In this comprehensive guide, we'll explore various approaches to enforce password complexity in Django projects.

Here's what we'll cover in this tutorial:

  • Django's built-in password validation system
  • Creating custom password validators
  • Configuring password complexity rules
  • Testing password validation
  • Best practices for password security

Here's what you need to know before getting started:

Frequently Asked Questions

You should have a basic understanding of Python and Django. Familiarity with Django forms, models, and the admin interface will be helpful. We'll be working with Django 4.2+ features, but most concepts apply to earlier versions as well.

Password complexity helps protect user accounts from brute force attacks, dictionary attacks, and credential stuffing. Strong passwords are the first line of defense in web application security, making it significantly harder for attackers to gain unauthorized access.

Django comes with several built-in password validators including UserAttributeSimilarityValidator, MinimumLengthValidator, CommonPasswordValidator, and NumericPasswordValidator. These provide a solid foundation for password security.

Yes! Django's password validation system is extensible. You can create custom validators by inheriting from django.contrib.auth.password_validation.BaseValidator and implementing your own validation logic.

Django provides a test command (python manage.py check --deploy) that can validate your password settings. You can also write custom tests to ensure your validation rules work as expected.

Understanding Django Password Validation

Django's password validation system is built around the concept of validators. Each validator is responsible for checking a specific aspect of password strength. The validation process runs when:

  • A user creates a new account
  • A user changes their password
  • An admin creates or modifies a user
  • Password validation is explicitly called

Let's start by examining Django's default password validation configuration:

Built-in Password Validators

Django provides several built-in validators that you can configure in your settings:

MinimumLengthValidator

This validator ensures passwords meet a minimum length requirement:

1
2
3
4
5
6
7
8
AUTH_PASSWORD_VALIDATORS = [
    {
        'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
        'OPTIONS': {
            'min_length': 8,
        }
    },
]

Purpose: Ensures passwords meet a minimum length requirement for better security.

What it checks:

  • Counts the total number of characters in the password
  • Rejects passwords shorter than the specified minimum length
  • In your configuration: minimum 10 characters

Example: With min_length: 10, passwords like "short" or "password" would be rejected, but "SecurePass123!" would be accepted.

Security Note: Longer passwords are exponentially harder to crack. A 10-character password is significantly more secure than an 8-character one.

UserAttributeSimilarityValidator

This validator prevents passwords from being too similar to user attributes like username or email:

1
2
3
4
5
6
7
8
9
AUTH_PASSWORD_VALIDATORS = [
    {
        'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
        'OPTIONS': {
            'max_similarity': 0.7,
            'user_attributes': ('username', 'first_name', 'last_name', 'email'),
        }
    },
]

Purpose: Prevents users from creating passwords that are too similar to their personal information.

What it checks:

  • Compares the password against the user's username, first name, last name, and email
  • Uses a similarity algorithm to detect if the password is too close to these attributes
  • Default similarity threshold is 0.7 (70% similarity)

Example: If a user's username is "danbrown", passwords like "danbrown123" or "DANBROWN" would be rejected.

Customization: You can adjust the similarity threshold and user attributes:

1
2
3
4
5
6
7
{
    'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
    'OPTIONS': {
        'max_similarity': 0.6,  # More strict (60% similarity)
        'user_attributes': ('username', 'first_name', 'last_name', 'email', 'phone'),
    }
}

CommonPasswordValidator

This validator checks against a list of common passwords:

1
2
3
4
5
AUTH_PASSWORD_VALIDATORS = [
    {
        'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
    },
]

Purpose: Prevents users from using commonly used passwords that are easily guessable.

What it checks:

  • Compares the password against a built-in list of 20,000+ common passwords
  • Includes passwords from data breaches and common patterns
  • Case-insensitive comparison

Examples of rejected passwords:

  • "password", "123456", "qwerty", "admin"
  • "letmein", "welcome", "monkey", "dragon"
  • "baseball", "football", "shadow", "master"

How it works: Django maintains this list internally and updates it with new versions. The list includes passwords from real data breaches, making it highly effective against dictionary attacks.

NumericPasswordValidator

This validator prevents entirely numeric passwords:

1
2
3
4
5
AUTH_PASSWORD_VALIDATORS = [
    {
        'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
    },
]

Purpose: Prevents users from using passwords that consist entirely of numbers.

What it checks:

  • Uses regex pattern ^\d+$ to check if the password contains only digits
  • Rejects passwords like "1234567890" or "0000000000"
  • Allows passwords that contain numbers mixed with letters/symbols

Examples:

  • Rejected: "1234567890", "0000000000", "987654321"
  • Accepted: "SecurePass123!", "MyPassword2024", "abc123def"

Why it's important: Numeric-only passwords are extremely weak and can be easily cracked through brute force attacks, even if they're long.

Creating Custom Password Validators

While Django's built-in validators are useful, you might need more specific requirements. Let's create custom validators for additional complexity rules.

Uppercase Letter Validator

This validator ensures passwords contain at least one uppercase letter:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
import re
from django.core.exceptions import ValidationError
from django.utils.translation import gettext as _

class UppercaseValidator:
    def validate(self, password, user=None):
        if not re.findall('[A-Z]', password):
            raise ValidationError(
                _("The password must contain at least 1 uppercase letter, A-Z."),
                code='password_no_upper',
            )

    def get_help_text(self):
        return _(
            "Your password must contain at least 1 uppercase letter, A-Z."
        )

Lowercase Letter Validator

This validator ensures passwords contain at least one lowercase letter:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
class LowercaseValidator:
    def validate(self, password, user=None):
        if not re.findall('[a-z]', password):
            raise ValidationError(
                _("The password must contain at least 1 lowercase letter, a-z."),
                code='password_no_lower',
            )

    def get_help_text(self):
        return _(
            "Your password must contain at least 1 lowercase letter, a-z."
        )

Number Validator

This validator ensures passwords contain at least one number:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
class NumberValidator:
    def validate(self, password, user=None):
        if not re.findall('\d', password):
            raise ValidationError(
                _("The password must contain at least 1 digit, 0-9."),
                code='password_no_number',
            )

    def get_help_text(self):
        return _(
            "Your password must contain at least 1 digit, 0-9."
        )

Special Character Validator

This validator ensures passwords contain at least one special character:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
class SymbolValidator:
    def validate(self, password, user=None):
        if not re.findall('[()[\]{}|\\`~!@#$%^&*_\-+=;:\'",<>./?]', password):
            raise ValidationError(
                _("The password must contain at least 1 special character: " +
                  "()[]{}|\`~!@#$%^&*_-+=;:'\",<>./?"),
                code='password_no_symbol',
            )

    def get_help_text(self):
        return _(
            "Your password must contain at least 1 special character: " +
            "()[]{}|\`~!@#$%^&*_-+=;:'\",<>./?"
        )

Advanced Custom Validator: ComplexityPasswordValidator

For more sophisticated password complexity requirements, you can create a configurable validator that uses Python's string module. This approach is more flexible and maintainable than individual validators:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
import string
from django.core.exceptions import ValidationError


class ComplexityPasswordValidator:
    """
    Validate the complexity of the password.
    """
    def __init__(self, min_lowercase=1, min_uppercase=1, min_numeric=1, min_non_alphanumeric=1):
        self.min_lowercase = min_lowercase
        self.min_uppercase = min_uppercase
        self.min_numeric = min_numeric
        self.min_non_alphanumeric = min_non_alphanumeric

    def validate(self, password, user=None):
        if self.min_lowercase and len([c for c in password if c in string.ascii_lowercase]) < self.min_lowercase:
            raise ValidationError(
                f"This password doesn't meet complexity standards ({self.min_lowercase} minimum lowercase letters)",
                code='password_insufficient_complexity',
            )
        if self.min_uppercase and len([c for c in password if c in string.ascii_uppercase]) < self.min_uppercase:
            raise ValidationError(
                f"This password doesn't meet complexity standards ({self.min_uppercase} minimum uppercase letters)",
                code='password_insufficient_complexity',
            )
        if self.min_numeric and len([c for c in password if c in string.digits]) < self.min_numeric:
            raise ValidationError(
                f"This password doesn't meet complexity standards ({self.min_numeric} minimum numeric characters)",
                code='password_insufficient_complexity',
            )
        if (self.min_non_alphanumeric and
                len([c for c in password if c in string.punctuation]) < self.min_non_alphanumeric):
            raise ValidationError(
                f"This password doesn't meet complexity standards ({self.min_non_alphanumeric} "
                f"minimum non-alphanumeric characters)",
                code='password_insufficient_complexity',
            )

    def get_help_text(self):
        help_text = f'Your password requires: '
        if self.min_lowercase:
            help_text += f"{self.min_lowercase} lowercase letters, "
        if self.min_uppercase:
            help_text += f"{self.min_uppercase} uppercase letters, "
        if self.min_numeric:
            help_text += f"{self.min_numeric} numeric characters, "
        if self.min_non_alphanumeric:
            help_text += f"{self.min_non_alphanumeric} non-alphanumeric characters, "

        if help_text[-2:] == ', ':
            help_text = help_text[:-2] + "."

        return help_text

This validator is more advanced than the individual validators we created earlier. Let's break down how it works:

The __init__ method allows you to configure the minimum requirements for each character type:

  • min_lowercase: Minimum number of lowercase letters (default: 1)
  • min_uppercase: Minimum number of uppercase letters (default: 1)
  • min_numeric: Minimum number of digits (default: 1)
  • min_non_alphanumeric: Minimum number of special characters (default: 1)

Example configurations:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
# Standard complexity (1 of each type)
validator = ComplexityPasswordValidator()

# High security (2 of each type)
validator = ComplexityPasswordValidator(
    min_lowercase=2, 
    min_uppercase=2, 
    min_numeric=2, 
    min_non_alphanumeric=2
)

# Custom requirements (no special characters required)
validator = ComplexityPasswordValidator(
    min_lowercase=1, 
    min_uppercase=1, 
    min_numeric=1, 
    min_non_alphanumeric=0  # No special characters required
)

This validator leverages Python's built-in string module, which provides predefined character sets:

  • string.ascii_lowercase: 'abcdefghijklmnopqrstuvwxyz'
  • string.ascii_uppercase: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
  • string.digits: '0123456789'
  • string.punctuation: '!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~'

How it works:

  1. Iterates through each character in the password
  2. Checks if the character belongs to the required character set
  3. Counts how many characters match each requirement
  4. Compares the count against the minimum requirement
  5. Raises ValidationError if any requirement is not met

The get_help_text() method dynamically generates help text based on the validator's configuration:

1
2
3
4
5
6
7
8
# Example outputs:
validator = ComplexityPasswordValidator()
print(validator.get_help_text())
# Output: "Your password requires: 1 lowercase letters, 1 uppercase letters, 1 numeric characters, 1 non-alphanumeric characters."

validator = ComplexityPasswordValidator(min_non_alphanumeric=0)
print(validator.get_help_text())
# Output: "Your password requires: 1 lowercase letters, 1 uppercase letters, 1 numeric characters."

Configuring the ComplexityPasswordValidator in Settings

To use this validator in your Django project, add it to your settings.py:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# settings.py
from .validators import ComplexityPasswordValidator

AUTH_PASSWORD_VALIDATORS = [
    {
        'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
        'OPTIONS': {
            'min_length': 10,
        }
    },
    {
        'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
    },
    # Add the complexity validator
    {
        'NAME': 'validators.ComplexityPasswordValidator',
        'OPTIONS': {
            'min_lowercase': 1,
            'min_uppercase': 1,
            'min_numeric': 1,
            'min_non_alphanumeric': 1,
        }
    },
]

Testing the ComplexityPasswordValidator

Here's how to test your custom validator:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
from django.test import TestCase
from django.core.exceptions import ValidationError
from .validators import ComplexityPasswordValidator

class ComplexityPasswordValidatorTestCase(TestCase):
    def setUp(self):
        self.validator = ComplexityPasswordValidator()

    def test_valid_password(self):
        """Test that valid passwords are accepted"""
        try:
            self.validator.validate('SecurePass123!')
        except ValidationError:
            self.fail("Valid password was rejected")

    def test_no_lowercase(self):
        """Test that passwords without lowercase are rejected"""
        with self.assertRaises(ValidationError):
            self.validator.validate('SECUREPASS123!')

    def test_no_uppercase(self):
        """Test that passwords without uppercase are rejected"""
        with self.assertRaises(ValidationError):
            self.validator.validate('securepass123!')

    def test_no_numbers(self):
        """Test that passwords without numbers are rejected"""
        with self.assertRaises(ValidationError):
            self.validator.validate('SecurePass!')

    def test_no_special_chars(self):
        """Test that passwords without special characters are rejected"""
        with self.assertRaises(ValidationError):
            self.validator.validate('SecurePass123')

    def test_custom_requirements(self):
        """Test custom complexity requirements"""
        custom_validator = ComplexityPasswordValidator(
            min_lowercase=2, 
            min_uppercase=2, 
            min_numeric=2, 
            min_non_alphanumeric=2
        )
        
        # Should pass
        custom_validator.validate('SecurePass123!@')
        
        # Should fail (only 1 uppercase)
        with self.assertRaises(ValidationError):
            custom_validator.validate('securePass123!@')

This validator offers several advantages over individual validators:

  • Configurable: Easy to adjust requirements without code changes
  • Maintainable: Single validator handles all complexity requirements
  • Flexible: Can require different minimums for each character type
  • Clear Error Messages: Specific feedback about which requirement failed
  • Dynamic Help Text: Automatically generates appropriate help text
  • Standards Compliant: Uses Python's official character definitions

Configuring Password Validation in Settings

Now let's configure password validation in your Django settings. Here's a practical example of how to set up AUTH_PASSWORD_VALIDATORS in your settings.py:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
# settings.py
AUTH_PASSWORD_VALIDATORS = [
    {
        'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
        'OPTIONS': {
            'min_length': 10,
        }
    },
    {
        'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
    },
]

How Password Validation works in Django

Let's break down each validator in this configuration and understand what it does:

Django executes validators in the order they appear in the list. This order is important because:

  • Early rejection: If an early validator fails, later ones won't run
  • Performance: Put faster, simpler validators first
  • User experience: Show the most important errors first

Recommended order:

  1. Length validation (fast, simple check)
  2. Numeric validation (quick regex check)
  3. Common password check (database lookup)
  4. User attribute similarity (more complex algorithm)

Adding Custom Validators

If you want to add the custom validators we created earlier (uppercase, lowercase, numbers, symbols), you can extend your configuration:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
# First, create validators.py with custom validators
# (See the custom validators section above)

# Then update settings.py
from .validators import (
    UppercaseValidator, 
    LowercaseValidator, 
    NumberValidator, 
    SymbolValidator
)

AUTH_PASSWORD_VALIDATORS = [
    {
        'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
        'OPTIONS': {
            'min_length': 10,
        }
    },
    {
        'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
    },
    # Custom validators
    {
        'NAME': 'validators.UppercaseValidator',
    },
    {
        'NAME': 'validators.LowercaseValidator',
    },
    {
        'NAME': 'validators.NumberValidator',
    },
    {
        'NAME': 'validators.SymbolValidator',
    },
]

Security Analysis of Your Configuration

Let's analyze the security strength of your specific configuration:

Security Analysis

Your Configuration Strengths:

  • 10-character minimum: Provides good protection against brute force attacks
  • Common password prevention: Blocks 20,000+ known weak passwords
  • User attribute protection: Prevents personal information-based passwords
  • Numeric-only prevention: Blocks extremely weak numeric passwords

Potential Improvements:

  • Add character variety requirements (uppercase, lowercase, symbols)
  • Consider increasing minimum length to 12+ for high-security applications
  • Implement password history to prevent reuse
  • Add rate limiting for password attempts

With your current configuration, let's calculate the theoretical password strength:

  • Character set: 26 lowercase + 26 uppercase + 10 digits + 33 symbols = 95 characters
  • Minimum length: 10 characters
  • Possible combinations: 95^10 combinations

Note: This assumes users actually use the full character set. Without character variety requirements, users might still choose weak passwords like "password123" or "qwertyuiop".

To make your configuration even more secure, consider these enhancements:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# Enhanced configuration with character variety
AUTH_PASSWORD_VALIDATORS = [
    {
        'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
        'OPTIONS': {
            'min_length': 12,  # Increased for better security
        }
    },
    {
        'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
    },
    # Add character variety requirements
    {
        'NAME': 'validators.UppercaseValidator',
    },
    {
        'NAME': 'validators.LowercaseValidator',
    },
    {
        'NAME': 'validators.NumberValidator',
    },
    {
        'NAME': 'validators.SymbolValidator',
    },
]

This enhanced configuration would require:

  • Minimum 12 characters
  • At least 1 uppercase letter
  • At least 1 lowercase letter
  • At least 1 number
  • At least 1 special character
  • Not similar to user attributes
  • Not in common password list
  • Not entirely numeric

Testing Password Validation

It's crucial to test your password validation rules. Here's how to create comprehensive tests:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
from django.test import TestCase
from django.contrib.auth.password_validation import validate_password
from django.core.exceptions import ValidationError
from django.contrib.auth.models import User

class PasswordValidationTestCase(TestCase):
    def test_password_too_short(self):
        """Test that passwords shorter than 8 characters are rejected"""
        with self.assertRaises(ValidationError):
            validate_password('short')

    def test_password_no_uppercase(self):
        """Test that passwords without uppercase letters are rejected"""
        with self.assertRaises(ValidationError):
            validate_password('password123!')

    def test_password_no_lowercase(self):
        """Test that passwords without lowercase letters are rejected"""
        with self.assertRaises(ValidationError):
            validate_password('PASSWORD123!')

    def test_password_no_number(self):
        """Test that passwords without numbers are rejected"""
        with self.assertRaises(ValidationError):
            validate_password('Password!')

    def test_password_no_symbol(self):
        """Test that passwords without special characters are rejected"""
        with self.assertRaises(ValidationError):
            validate_password('Password123')

    def test_valid_password(self):
        """Test that valid passwords are accepted"""
        try:
            validate_password('SecurePass123!')
        except ValidationError:
            self.fail("Valid password was rejected")

    def test_password_similar_to_username(self):
        """Test that passwords similar to username are rejected"""
        user = User.objects.create_user(username='testuser')
        with self.assertRaises(ValidationError):
            validate_password('testuser123!', user=user)

Advanced Password Validation Techniques

Password Strength Meter

You can create a password strength meter to provide real-time feedback to users:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
import re
from django.core.exceptions import ValidationError

class PasswordStrengthValidator:
    def validate(self, password, user=None):
        score = 0
        
        # Length check
        if len(password) >= 8:
            score += 1
        if len(password) >= 12:
            score += 1
            
        # Character variety checks
        if re.search(r'[A-Z]', password):
            score += 1
        if re.search(r'[a-z]', password):
            score += 1
        if re.search(r'\d', password):
            score += 1
        if re.search(r'[()[\]{}|\\`~!@#$%^&*_\-+=;:\'",<>./?]', password):
            score += 1
            
        # Bonus for mixed case and numbers
        if re.search(r'[A-Z].*[a-z]|[a-z].*[A-Z]', password):
            score += 1
        if re.search(r'\d.*[A-Za-z]|[A-Za-z].*\d', password):
            score += 1
            
        if score < 4:
            raise ValidationError(
                f"Password strength too weak (score: {score}/8). "
                "Include uppercase, lowercase, numbers, and special characters.",
                code='password_too_weak',
            )

    def get_help_text(self):
        return _(
            "Your password must be strong enough (score 4/8 or higher). "
            "Include uppercase, lowercase, numbers, and special characters."
        )

Dictionary Attack Protection

Create a custom validator to check against a custom dictionary of common passwords:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class CustomDictionaryValidator:
    def __init__(self, dictionary_file=None):
        self.dictionary_file = dictionary_file or 'common_passwords.txt'
        self.common_passwords = self._load_dictionary()
    
    def _load_dictionary(self):
        try:
            with open(self.dictionary_file, 'r') as f:
                return set(line.strip().lower() for line in f)
        except FileNotFoundError:
            return set()
    
    def validate(self, password, user=None):
        if password.lower() in self.common_passwords:
            raise ValidationError(
                "This password is too common. Please choose a more unique password.",
                code='password_too_common',
            )

    def get_help_text(self):
        return _(
            "Your password cannot be a commonly used password."
        )

Best Practices for Password Security

Here are some best practices to follow when implementing password complexity rules:

Security Guidelines

  • Minimum Length: Require at least 8 characters, preferably 12+ for sensitive applications
  • Character Variety: Require uppercase, lowercase, numbers, and special characters
  • Common Password Prevention: Check against lists of common passwords
  • User Attribute Similarity: Prevent passwords similar to username, email, or other user data
  • Password History: Prevent reuse of recent passwords
  • Rate Limiting: Implement rate limiting for password attempts

User Experience Considerations

  • Clear Requirements: Display password requirements clearly to users
  • Real-time Feedback: Provide immediate feedback on password strength
  • Helpful Messages: Give specific guidance when validation fails
  • Password Generators: Offer secure password generation tools
  • Progressive Disclosure: Show requirements as users type

Monitoring and Auditing

Implement logging and monitoring for password-related activities:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import logging
from django.contrib.auth.signals import user_logged_in, user_login_failed
from django.dispatch import receiver

logger = logging.getLogger('security')

@receiver(user_logged_in)
def log_user_login(sender, request, user, **kwargs):
    logger.info(f'User {user.username} logged in successfully from {request.META.get("REMOTE_ADDR")}')

@receiver(user_login_failed)
def log_user_login_failed(sender, request, credentials, **kwargs):
    logger.warning(f'Failed login attempt for user {credentials.get("username")} from {request.META.get("REMOTE_ADDR")}')

# Custom middleware for password change logging
class PasswordChangeMiddleware:
    def __init__(self, get_response):
        self.get_response = get_response

    def __call__(self, request):
        response = self.get_response(request)
        
        if request.method == 'POST' and 'password_change' in request.path:
            if request.user.is_authenticated:
                logger.info(f'Password changed for user {request.user.username}')
        
        return response

Conclusion

Implementing robust password complexity rules in Django is essential for maintaining application security. By combining Django's built-in validators with custom validation logic, you can create a comprehensive password security system that protects your users while maintaining a good user experience.

Remember that password complexity is just one aspect of security. Always combine it with other security measures such as:

  • HTTPS enforcement
  • Rate limiting
  • Account lockout policies
  • Multi-factor authentication
  • Regular security audits

The code examples provided in this tutorial can be adapted and extended to meet your specific security requirements. Always test your password validation thoroughly and keep your security measures up to date with the latest best practices.

Security Best Practices

Security Note: Password complexity rules should be part of a comprehensive security strategy. Consider implementing additional measures like account lockout, CAPTCHA for failed attempts, and monitoring for suspicious activities.

Additional Resources

To learn more about Django security and password validation, check out these resources: