Pular para o conteúdo principal

Password Validation

The PasswordDefinition class provides comprehensive password strength validation and generation capabilities.

Basic Usage

Creating a Password Definition

<?php
use ByJG\Authenticate\Definition\PasswordDefinition;
use ByJG\Authenticate\Model\UserModel;

// Create password definition with default rules
$passwordDefinition = new PasswordDefinition();

// Attach to user model
$userModel = new UserModel();
$userModel->withPasswordDefinition($passwordDefinition);

// Now password is validated when set
$userModel->setPassword('WeakPwd'); // Throws InvalidArgumentException

Applying Rules Through UsersService

Instead of attaching the definition to every UserModel manually, pass it to the UsersService constructor. Every entity created through the service (e.g., via addUser() or getUsersEntity()) receives the same PasswordDefinition.

<?php
use ByJG\Authenticate\Definition\PasswordDefinition;
use ByJG\Authenticate\Enum\LoginField;
use ByJG\Authenticate\Service\UsersService;

$passwordDefinition = new PasswordDefinition([
PasswordDefinition::MINIMUM_CHARS => 12,
PasswordDefinition::REQUIRE_NUMBERS => 2,
]);

$users = new UsersService(
$usersRepo,
$propsRepo,
LoginField::Username,
$passwordDefinition
);

$newUser = $users->addUser('Jane', 'jane', '[email protected]', 'StrongPassword123!');

Need a blank entity with the validation rules already loaded? Use getUsersEntity():

<?php
$user = $users->getUsersEntity([
'name' => 'John Doe',
'email' => '[email protected]',
]);
$user->setPassword('AnotherStrongPass123!');
$users->save($user);

This approach guarantees that every model created or retrieved through the service (getById(), getByEmail(), getUsersByProperty(), etc.) automatically enforces your password policy.

Password Rules

Default Rules

The default password policy requires:

RuleDefault ValueDescription
minimum_chars8Minimum password length
require_uppercase0Number of uppercase letters required
require_lowercase1Number of lowercase letters required
require_symbols0Number of symbols required
require_numbers1Number of digits required
allow_whitespace0Allow whitespace characters (0 = no)
allow_sequential0Allow sequential characters (0 = no)
allow_repeated0Allow repeated patterns (0 = no)

Custom Rules

<?php
use ByJG\Authenticate\Definition\PasswordDefinition;

$passwordDefinition = new PasswordDefinition([
PasswordDefinition::MINIMUM_CHARS => 12,
PasswordDefinition::REQUIRE_UPPERCASE => 2,
PasswordDefinition::REQUIRE_LOWERCASE => 2,
PasswordDefinition::REQUIRE_SYMBOLS => 1,
PasswordDefinition::REQUIRE_NUMBERS => 2,
PasswordDefinition::ALLOW_WHITESPACE => 0,
PasswordDefinition::ALLOW_SEQUENTIAL => 0,
PasswordDefinition::ALLOW_REPEATED => 0
]);

Setting Individual Rules

<?php
$passwordDefinition = new PasswordDefinition();
$passwordDefinition->setRule(PasswordDefinition::MINIMUM_CHARS, 10);
$passwordDefinition->setRule(PasswordDefinition::REQUIRE_UPPERCASE, 1);
$passwordDefinition->setRule(PasswordDefinition::REQUIRE_SYMBOLS, 1);

Validating Passwords

Validation Result Codes

The matchPassword() method returns a bitwise result:

<?php
$result = $passwordDefinition->matchPassword('weak');

if ($result === PasswordDefinition::SUCCESS) {
echo "Password is valid";
} else {
// Check specific failures
if ($result & PasswordDefinition::FAIL_MINIMUM_CHARS) {
echo "Password is too short\n";
}
if ($result & PasswordDefinition::FAIL_UPPERCASE) {
echo "Missing uppercase letters\n";
}
if ($result & PasswordDefinition::FAIL_LOWERCASE) {
echo "Missing lowercase letters\n";
}
if ($result & PasswordDefinition::FAIL_NUMBERS) {
echo "Missing numbers\n";
}
if ($result & PasswordDefinition::FAIL_SYMBOLS) {
echo "Missing symbols\n";
}
if ($result & PasswordDefinition::FAIL_WHITESPACE) {
echo "Whitespace not allowed\n";
}
if ($result & PasswordDefinition::FAIL_SEQUENTIAL) {
echo "Sequential characters detected\n";
}
if ($result & PasswordDefinition::FAIL_REPEATED) {
echo "Repeated patterns detected\n";
}
}

Available Failure Codes

ConstantValueDescription
SUCCESS0Password is valid
FAIL_MINIMUM_CHARS1Password too short
FAIL_UPPERCASE2Missing uppercase letters
FAIL_LOWERCASE4Missing lowercase letters
FAIL_SYMBOLS8Missing symbols
FAIL_NUMBERS16Missing numbers
FAIL_WHITESPACE32Whitespace not allowed
FAIL_SEQUENTIAL64Sequential characters detected
FAIL_REPEATED128Repeated patterns detected

Password Generation

Generate a Random Password

<?php
$password = $passwordDefinition->generatePassword();
echo $password; // e.g., "aB3dE7fG9"

Generate Longer Passwords

<?php
// Generate a password 5 characters longer than the minimum
$password = $passwordDefinition->generatePassword(5);

The generated password will:

  • Meet all defined rules
  • Be cryptographically random
  • Avoid sequential and repeated patterns

User Registration with Password Validation

Complete Example

<?php
use ByJG\Authenticate\Definition\PasswordDefinition;
use ByJG\Authenticate\Model\UserModel;

// Define password rules
$passwordDefinition = new PasswordDefinition([
PasswordDefinition::MINIMUM_CHARS => 10,
PasswordDefinition::REQUIRE_UPPERCASE => 1,
PasswordDefinition::REQUIRE_LOWERCASE => 1,
PasswordDefinition::REQUIRE_SYMBOLS => 1,
PasswordDefinition::REQUIRE_NUMBERS => 1,
]);

// Create user with password validation
try {
$user = new UserModel();
$user->withPasswordDefinition($passwordDefinition);

$user->setName('John Doe');
$user->setEmail('[email protected]');
$user->setUsername('johndoe');
$user->setPassword($_POST['password']); // Validated automatically

$users->save($user);
echo "User created successfully";

} catch (InvalidArgumentException $e) {
echo "Password validation failed: " . $e->getMessage();
}

User-Friendly Error Messages

<?php
function getPasswordErrors(int $result): array
{
$errors = [];

if ($result & PasswordDefinition::FAIL_MINIMUM_CHARS) {
$errors[] = "Password must be at least 8 characters long";
}
if ($result & PasswordDefinition::FAIL_UPPERCASE) {
$errors[] = "Password must contain at least one uppercase letter";
}
if ($result & PasswordDefinition::FAIL_LOWERCASE) {
$errors[] = "Password must contain at least one lowercase letter";
}
if ($result & PasswordDefinition::FAIL_NUMBERS) {
$errors[] = "Password must contain at least one number";
}
if ($result & PasswordDefinition::FAIL_SYMBOLS) {
$errors[] = "Password must contain at least one symbol (!@#$%^&*()...)";
}
if ($result & PasswordDefinition::FAIL_WHITESPACE) {
$errors[] = "Password cannot contain whitespace";
}
if ($result & PasswordDefinition::FAIL_SEQUENTIAL) {
$errors[] = "Password cannot contain sequential characters (abc, 123, etc.)";
}
if ($result & PasswordDefinition::FAIL_REPEATED) {
$errors[] = "Password cannot contain repeated patterns";
}

return $errors;
}

// Usage
$result = $passwordDefinition->matchPassword($_POST['password']);
if ($result !== PasswordDefinition::SUCCESS) {
$errors = getPasswordErrors($result);
foreach ($errors as $error) {
echo "- " . $error . "\n";
}
}

Sequential and Repeated Patterns

Sequential Characters

Sequential patterns that are detected include:

  • Alphabetic: abc, bcd, cde, xyz, etc. (case-insensitive)
  • Numeric: 012, 123, 234, 789, 890, etc.
  • Reverse: 987, 876, 765, 321, etc.

Repeated Patterns

Repeated patterns include:

  • Repeated characters: aaa, 111, etc.
  • Repeated sequences: ababab, 123123, etc.

Password Change Flow

<?php
// Password change with validation
try {
$user = $users->getById($userId);
$user->withPasswordDefinition($passwordDefinition);

// Verify old password
$existingUser = $users->isValidUser($user->getUsername(), $_POST['old_password']);
if ($existingUser === null) {
throw new Exception("Current password is incorrect");
}

// Set new password (validated automatically)
$user->setPassword($_POST['new_password']);
$users->save($user);

echo "Password changed successfully";

} catch (InvalidArgumentException $e) {
echo "New password validation failed: " . $e->getMessage();
}

Best Practices

  1. Balance security and usability - Don't make rules too restrictive
  2. Educate users - Provide clear error messages
  3. Use password generation - Offer to generate strong passwords
  4. Consider passphrases - Allow longer passwords with spaces if appropriate
  5. Combine with rate limiting - Prevent brute force attacks

Next Steps